import pygame
import random as rd
from dataclasses import dataclass
#import time
import traceback
import os
import pygame.freetype
#import psutil
import typing #240104
import sys#240528
import threading, time #241029
from queue import Queue

pygame.init()
_VERSION_ = '1.0.0.Alpha_23w04a'

# 2023/05/27
_VERSION_ = '1.1.5-Beta_23w05a'# bedwar, player & 3 enemy, infohud
_VERSION_ = '1.1.6-Release' # restart game, kill/died info
_VERSION_ = '1.2-Release' # 修改重启和炸弹更新区块逻辑 
_VERSION_ = '1.2.1-Release' # 修复实体击杀数和复活数显示bug

# 2023/05/28
_VERSION_ = '1.2.2-Beta' # 修复实体击杀数和复活数显示bug
_VERSION_ = '1.3' # 添加mod, 可以修改配置, 函数。
_VERSION_ = '1.3.1-Release' # ...
_VERSION_ = '1.4.1' #添加渲染器和着色器
_VERSION_ = '1.5-Beta' #添加更多mod
_VERSION_ = '1.5.1' #enemy set blocks
_VERSION_ = '1.6.1' #添加时间计算
_VERSION_ = '1.6.2' #修复少量bug

# 2023/05/29
_VERSION_ = '1.6.3 - 1.7' # add Iris mod,修改enemy颜色区别， 添加1.7部分内容（传送，按WASD往四个方向移动5m）,printlib
_VERSION_ = '1.6.4' # 修复开始界面显示bug，-y方向方块无法破坏的bug
_VERSION_ = '1.6.5' # 修复infohud显示不全bug

# 2023/06/08
_VERSION_ = '1.7' # 修改printlib, cfg, 加入暂停功能
_VERSION_ = '1.7.1' # fix up some bugs in v1.7.
_VERSION_ = '1.7.2' # *** 修改炸弹计算逻辑 ***

# 2023/06/12
_VERSION_ = '1.7.3' # fix up render bugs, 加入简化输出信息选项, 暂停恢复后清除暂停消息字幕
# 2023/06/12 - 2023/06/13
_VERSION_ = '1.7.4' # add TNT
_VERSION_ = '1.7.5' # 增加事件提示, 修复TNT的bug
_VERSION_ = '1.7.6' # 修复分数增加减少不正确的bug, 增大enemy投掷TNT的范围, 增加TNT爆炸后环境的渲染
_VERSION_ = '1.7.7' # 修改玩家面对方向改变的逻辑, 不用移动也可改变朝向(player.facing)

# 2023/06/14
_VERSION_ = '1.7.8' # add TitleBar, 修改事件提示(add tip to capacity), 实现分散敌人的功能, 加入纯GUI模式
_VERSION_ = '1.7.9' # 可修改FPS
_VERSION_ = '1.7.10'# add Jade.py, changeable resource, 炸弹爆炸时间与fps无关

# *** ***  1.7:战斗更新  *** *** #
# *** ***  1.8:AI更新  *** *** #

# 2023/06/18
_VERSION_ = '1.7.11' # 修改部分AI, 敌人有更高几率寻找床(?未实现)和玩家, 加快分数增加速度 (including 1.8), add vsync mode 
                    # plan:TNTBreakBlocks
# 2023/06/19
_VERSION_ = '1.8' #在enemy溢出时报错, 取消胜利后的暂停, 欢迎界面显示语句(只有第一次进入时显示), 修复因末影珍珠可能导致的出界问题
_VERSION_ = '1.8.1' # 每次进入开始界面更新欢迎语句, 命令行输出事件消息受config控制, UI界面事件提醒更清楚(TitleBarPrint)
_VERSION_ = '1.8.2' # 增加对CPU和内存的显示
_VERSION_ = '1.8.3' # 增强AI, enemy不会往一些危险的地方移动
_VERSION_ = '1.8.4' # 减小enemy远离player时放置炸弹的概率, TNT break block, TNT爆炸时间缩短, 提高投掷TNT所需积分, TNT爆炸范围增大
        # 1.9内容: ice thorn cold and slow effect

# *** ***  1.9:状态效果更新  *** *** #
# 2023/06/20
_VERSION_ = '1.9' #修复重生时slow状态无法正常清除的bug, 加入shield
_VERSION_ = '1.9.1' # 修复shield功能不正常的bug, 加入状态指示器, 修复提示信息显示重叠的bug 

# *** ***  1.10:???  *** *** #
# 2023/10/25
_VERSION_ = '1.10_Prev1' # python3.11+VSCode移植版, 1.10预览版
_VERSION_ = '1.10_Prev2' # 加入传送细节显示, jade.tt Texture file

# 2024/1/3
_VERSION_ = '1.10_beta1' # 修复炸弹爆炸时可能出现的显示不全的问题, 优化炸弹算法
_VERSION_ = '1.10_beta2' # continue fix bugs. :)
_VERSION_ = '1.10'   # ++__slots__, 移除无用代码
_VERSION_ = '1.10.1' #加入TNT扔出的动画

# *** ***  1.11:模式更新  *** *** #
# *** ***  1.12:PlentifulUpdate  *** *** #
# 2024/1/3
_VERSION_ = '1.11_Prev1' # 改变地图数据类型(int->class)
_VERSION_ = '1.11.1' # 加入Normal模式, Soft类型的障碍会爆金币
_VERSION_ = '1.11.2' # Normal mode 禁止玩家放置方块, 修复物资刷新位置错误的bug
_VERSION_ = '1.11.3' # add shield_drop in Normal mode. 更新Enemy的AI, 使其不会在被方块包围的时候放置炸弹(Experimental)
_VERSION_ = '1.11.4' # 加入双人模式和PVP模式, 此模式下FriendFire选项将强制开启
# 2024/01/04
_VERSION_ = '1.11.5' # 更新开始界面, 加入火箭
_VERSION_ = '1.12_Prev1' # AI update 2.0, enemy放置炸弹前再加入一项检查
_VERSION_ = '1.12' # Enemy try to find no-barrier and safer way when unsafe
# 20240105
_VERSION_ = '1.12.1' # calcGoalInNormalMode, LevelMode. 减少TNT爆炸等待时间(1600->randint(169,696))
                     # 降低enemy远离玩家时放炸弹的概率(5->4), AI update(Enemy on unsafe, Enemy throw TNT)
                     # Enemy会检查火箭能否射中Player
# 20240106
_VERSION_ = '1.12.2' # add Diamond(addon, can add score in level mode). fix bug:Enemy can shoot rocket after died and tp in normal mode.
                     # add *mine, update help, show score, leveled pvp. optimize tick counter. add time-score(Single-Normal-Level Only). 
                     # Enemy TNT throw cooldown(18 tick = 900ms)
# 20240107
_VERSION_ = '1.12.3' # fix bug: Enemy's incorrect power(normal mode.)
# 20240110
_VERSION_ = '1.12.4' # using Cython to speed-up, build exefile, quickdraw.mod, 修改炸弹爆炸逻辑(爆炸后再计算范围)

# 2024-05-28
_VERSION_ = '1.13'   # 程序启动参数
_VERSION_ = '1.13.1' # 削弱TNT默认威力(4->3)

_VERSION_ = '1.14'   # 调整normal模式下enemy扔TNT的安全距离(2->3)
_VERSION_ = '1.14.1' # enemy遇见TNT会逃跑, 增强寻路算法(discard)
_VERSION_ = '1.15'   # 加入更多block类型, good idea?
_VERSION_ = '1.15.1' # add slime_block
_VERSION_ = '1.15.2' # add fire, jump
_VERSION_ = '1.16'   # deprecated 1.15 as a bi. 加入小游戏

# 2024-05-29
_VERSION_ = '1.16.1' # invisible_war, switchplace
_VERSION_ = '1.16.2' # fix, add time display, rocket & tnt game, enemy使用火箭添加冷却时间
# 24-05-31
_VERSION_ = '1.16.3' # apple
# 24-10-28
_VERSION_ = '1.16.4' # beta, 重写1.14的算法。Enemy在危险环境中将寻找最近的安全点并前往那里。
# 24-10-29
_VERSION_ = '1.16.5' # beta, Enemy能够逃离TNT
_VERSION_ = '1.16.6' # beta, 修复queue偶现的阻塞问题
_VERSION_ = '1.17'   # 调整小游戏内容：加入隐身时间限制, 火箭竞速中自动添加盾牌, 增大时间间隔等。部分界面文字使用深色字体。

"""
1格: 45x45 60x60
map: 27x20 20x15
using 60x60 & 20x15.

  0: 1 2 3 4 ... ... 15 16 17 18 19   (3, 16)
0
:
1
2
3
.
.
.
11
12
13
14


(3, 11)

"""

pixelpos_t : typing.TypeAlias = int
blockpos_t : typing.TypeAlias = int

# 1.13, default
X_BLOCKLEN: int = 20
Y_BLOCKLEN: int = 15
X_SCREXTRA: int = 300
QB_DEFAULT_CAPTION: str = f'({rd.randint(1200, 1999)},{rd.randint(800, 999)})QBomb Game {_VERSION_}'
QB_FONT_NAME: str = 'C:/Windows/Fonts/simhei.ttf'
QB_FONT_SIZE: int = 0x0e
#1.13
for sys_argv in sys.argv:
    if sys_argv.count('=') == 1:
        sysstr = sys_argv.split('=')
        if sysstr[0] == 'xextra':
            X_SCREXTRA = int(sysstr[1])
        elif sysstr[0] == 'caption':
            QB_DEFAULT_CAPTION = sysstr[1].replace('@@@', ' ').replace('$$$', f'{_VERSION_}')
        elif sysstr[0] == '--fontname':
            QB_FONT_NAME = sysstr[1]
        elif sysstr[0] == '--fontsize':
            QB_FONT_SIZE = int(sysstr[1])
        else:
            pass
    else:
        pass
        
X_SCRLEN : int  = X_BLOCKLEN * 60 + X_SCREXTRA
Y_SCRLEN : int  = Y_BLOCKLEN * 60

assert(180 < X_SCRLEN < 1800)
assert(180 < Y_SCRLEN < 1200)


screen = pygame.display.set_mode((X_SCRLEN, 900))
pygame.display.set_caption(QB_DEFAULT_CAPTION)
f = pygame.font.Font(QB_FONT_NAME, QB_FONT_SIZE)
fps = pygame.time.Clock()

@dataclass
class NewVersionTip:
    isNewTry: bool = False
    string  :  str = '这是一个beta版本。' #'该版本包含1.17版本部分新内容。'

# 1.16.4
class ObsoleteException(Exception):
    def __init__(self, message):
        self.message = message

    def __str__(self):
        return f"ObsoleteException: {self.message}"

# 1.16.5
class MonitorSignal(object):
    '''监控程序状态'''
    def __init__(self) -> None:
        self.starting: bool = False
        self.tickClock: int = 0
        self.stack_record: str = 'None'
    def Main(self):
        printfn = print if not mods.CrystalLib.Exist_Argv("--NoPrompt") else (lambda anyargs: "wow")
        while True:
            time.sleep(1)
            if self.starting == False:
                break
            if self.tickClock <= 0:
                #print_warning(f'Main thread died: \n{self.stack_record}')
                print_warning(f'Main thread died:\n')
                for _thread in threading.enumerate():
                    printfn(f'Thread ID: {_thread.ident}, Name: {_thread.name}')
                    try:
                        traceback.print_stack(sys._current_frames()[_thread.ident])
                    except Exception as e:
                        printfn(f'stack exception: {e} at thread_call_id {_thread.ident}')
                    printfn()
                self.close()
                sys.exit(1)
                raise ProcessLookupError()
            else:
                self.tickClock = 0
    def start(self, stackvalue: str):
        self.starting = True
        self.stack_record = stackvalue
        threading.Thread(target=self.Main).start()
    def close(self):
        self.starting = False
    def greet(self):
        self.tickClock += 1
Monitor = MonitorSignal()
def ftps():
    return pygame.time.get_ticks()
def cint(c):
    return c if type(c) is int else int(c)
def prob(p):
    return rd.randint(0, 100) < p

_MODS_COUNT_ = 0
#import mods
import mods.CrystalLib
mods.CrystalLib.printm('Loading mod: crystal')
mods.CrystalLib.inits(fps, _VERSION_)
_MODS_COUNT_ += 1
mods.CrystalLib.printm(f'Load version: {_VERSION_}', True)
#print()
import mods.cfg
mods.CrystalLib.printm('Loading mod: cfg')
_MODS_COUNT_ += 1
#import Shaders.blockworld as bw
#printm('Loading mod: Shaders')
#mods.CrystalLib.printm(f'Loading shader: {bw.Shader_name()}')
#_MODS_COUNT_ += 1
import mods.Maplib
mods.Maplib.inits()
Map : list[list[mods.Maplib.MapObject]]
_MODS_COUNT_ += 1
import mods.EntityLib
mods.EntityLib.inits()
_MODS_COUNT_ += 1
import mods.printlib
mods.printlib.inits(screen)
_MODS_COUNT_ += 1
import mods.Iris
mods.Iris.inits(screen)
_MODS_COUNT_ += 1
import Shaders.quickdraw
Shaders.quickdraw.Init(screen)
_MODS_COUNT_ += 1
def printm(m, g = False):
    #print(f'[{time.ctime()[11:-5]}] [INFO] {m}')
    mods.CrystalLib.printm(m, g)

WARN_TOTAL = 0
def print_warning(w):
    global WARN_TOTAL
    WARN_TOTAL += 1
    mods.CrystalLib.print_warning(w)
    if WARN_TOTAL >= 16:
        raise ValueError('[WARN COUNTER]: more than 8 warning or error occured.')
    """print(f'[{time.ctime()[11:-5]}] [WARN/ERROR] {w}')
    WARN_TOTAL += 1
    if WARN_TOTAL >= 10:
        raise ValueError('[WARN COUNTER]: more than 60 warning or error.')
    """

# gamemode
GM_BEDWAR : int = 1
GM_NORMAL : int = 2
GM_NOW : int = 0
#printm(f'Load version: {_VERSION_}', True)

#1.12.1
#gamebuff
GB_NOW : int = 0
GB_LEVEL : int = 0b0001
GB_LEVEL_SHUT : bool = False
GB_LEVEL_LVL : int = 0
KILLSCORE : int = 320
class LevelGoals:
    __slots__ = 'scores', 'timescore', 'totalscore'
    def __init__(self) -> None:
        self.scores : list[int] = [0, 0, 0, 0]
        self.timescore : list[int] = [0,0,0,0]
        self.totalscore: list[int] = [0,0,0,0]

# statistic
ST_LEVELGOALS : LevelGoals = LevelGoals()

@dataclass(slots=True)#slots=True)
class Bomb:
    #__slots__ = (
    #    'x', 'y', 'power', 'Time', 'belong', 'color', 'ID', 'xadd', 'xsub', 'yadd', 'ysub'
    #)
    x     : int = 0
    y     : int = 0
    power : int = 2
    Time  : int = 2_200  #44 + 16    # tps 50, 1s = 20 tick, record Time = ftps() + 44
    belong: int = 0
    color : tuple = (255, 90, 60)  #(0, 0xff, 0)
    ID    : int = 4
    xadd  : int = 0
    xsub  : int = 0
    yadd  : int = 0
    ysub  : int = 0
    HadCalcedRange : bool = False
    #ctype : str = 'bomb'

@dataclass(slots=True)
class bbs:
    #__slots__ = 'x', 'y', 'color', 'ID', 'towards'
    x:int #= 0
    y:int #= 0
    color:tuple #= (255,90,60)
    ID:int #= 5
    towards : int #= 1 # 1: n-s(y), 2: w-e(x)
    
Void = 0
Soft = 1
Hard = 2
Bed  = 3
BB   = 4
BB2  = 5
def reLoad():
    global Map
    Map = mods.Maplib.reLoad_bedwar_0()

def draw_block(x, y, dtype):
    # x=1~20,  y=1~15
    mods.Iris.draw_block(x, y, dtype, screen)
    

blist : list[Bomb] = []
# 20240102
def CreateBomb(this : Bomb):
    blist.append(this)

def Bomb_CalcRange(this : Bomb):
    x = this.x
    y = this.y
    for i in range(this.power - 1):
        x -= 1
        if x < 0:
            x = 0
            break
        if Map[y][x].mtype == Void:
            pass  # can continue
        else:
            if Map[y][x].mtype not in (Soft, Bed):
                x += 1
            break # find barrier, break
    this.xsub = x
    x = this.x
    for _ in range(this.power - 1):
        x += 1
        if x >= 20:
            x = 19
            break
        if Map[y][x].mtype == Void:
            pass
        else:
            if Map[y][x].mtype not in (Soft, Bed):
                x -= 1
            break
    this.xadd = x
    x = this.x
    for _ in range(this.power - 1):
        y -= 1
        if y < 0:
            y = 0
            break
        if Map[y][x].mtype == Void:
            pass
        else:
            if Map[y][x].mtype not in (Soft, Bed):
                y += 1
            break
    this.ysub = y
    y = this.y
    for _ in range(this.power - 1):
        y += 1
        if y >= 15:
            y = 14
            break
        if Map[y][x].mtype == Void:
            pass
        else:
            if Map[y][x].mtype not in (Soft, Bed):
                y -= 1
            break
    this.yadd = y
    #blist.append(this)


def hasb(x, y):
    # has bomb?
    if blist == []:
        return False
    for bs in blist:
        if bs.x == x and bs.y == y:
            return True
    return False
def Draw_BombAround(bs):
    for x in range(max(0, bs.x - bs.power + 1), min(bs.x + bs.power, 20)):
        for y in range(max(0, bs.y - bs.power + 1), min(bs.y + bs.power, 15)):
            mods.Iris.draw_block(x, y, Map[y][x].mtype)



def EulaDis(x, y, a, b):
    return ((x - a) ** 2 + (y - b) ** 2) ** (1 / 2)
def MhtDis(x, y, a, b):
    return abs(abs(x - a) + abs(y - b))

# 20240104
@dataclass(slots=True)
class Rocket():
    x : pixelpos_t
    y : pixelpos_t
    ID: int
    belong : int
    direct : str # 'west', 'south', 'east', 'north'
    live : bool

RocketList : list[Rocket] = []
def AddRocket(xb: blockpos_t, yb: blockpos_t, DIRECT: str, From: int):
    RocketList.append(
        Rocket(
            xb*60 + 30, yb*60 + 30, 8, From, DIRECT, True
        )
    )

@dataclass(slots=True)
class Landmine():
    x : blockpos_t
    y : blockpos_t
    belong : int
    set_time : int = 500 #ftps()
    blast : int | None | bool = None # not bommb is none, boomed is last_time(1200 + ftps()), bool(False) is pre-bomb
    ID : int = 9

MineList : list[Landmine] = []

@dataclass(slots=True)
class TNT():
    #__slots__ = 'x', 'y', 'belong', 'ID', 'Time', 'xFrom', 'yFrom', 'vy', 'VY', 'xadd', 'yadd', 'xpos', 'ypos'
    x      : int
    y      : int
    belong : int
    ID     : int = 6
    Time   : int = 1_600 + ftps()  # tps 20, 2.4s
    xFrom  : int = 0
    yFrom  : int = 0
    vy     : int = 0 # 22 dx=abs(xf-x)*60 t=dx/22/2 v=gt
    VY     : int = 0
    xadd   : float = 0
    yadd   : float = 0
    xpos   : float = 0
    ypos   : float = 0
    TNTTPS : int = rd.randint(169, 696)
TNTList : list[TNT] = []
def Create_TNT(t: TNT):
    t.xpos = t.xFrom * 60 + 30
    t.ypos = t.yFrom * 60 + 30
    t.vy = 11 * 3 #abs(t.xFrom - t.x) * 60 / 44 * 2
    t.VY = -t.vy
    if t.x != t.xFrom:
        t.xadd = (t.x - t.xFrom) * 60 / 22
    else:
        t.yadd = (t.y - t.yFrom) * 60 / 22
    TNTList.append(t)

def MovTNTWhenThrowing(t: TNT):
    t.vy -= 3
    if t.xadd != 0:
        t.xpos += t.xadd
        t.ypos -= t.vy 
    else:
        t.ypos += t.yadd

# 1.16.4
class Coordinate2i(object):
    '''储存两个int坐标值'''
    def __init__(self, x:int, y:int, state: typing.Any = None) -> None:
        self.x: int = x
        self.y: int = y
        self.state = state

#def RenderTNT_onThrow(t : TNT):
#    screen.blit(_item_tnt, (mods.CrystalLib.cint(who.x*60 + 6), mods.CrystalLib.cint(who.y*60 + 6)))

def MovTNT(pl, e1, e2, e3):
    global TNTList
    tli = []
    TTPS = ftps()
    for t in TNTList:
        #t.Time -= 1
        if t.vy > t.VY:
            MovTNTWhenThrowing(t)
            t.Time = t.TNTTPS + TTPS
            tli.append(t)
            continue
            
        if TTPS >= t.Time:
            # Booooooooooooooooomb !!!
            
            # break blocks
            if mods.cfg.EnableTNTBreakBlock == True:
                if mods.cfg.UseEulaDistance:
                    for x in range(max(t.x-mods.cfg.TNTPower, 0), min(t.x + mods.cfg.TNTPower + 1, 20)):
                        for y in range(max(t.y -mods.cfg.TNTPower, 0), min(t.y + mods.cfg.TNTPower + 1, 15)):
                            if len(MineList) != 0:
                                for lm in MineList:
                                    if lm.x == x  and lm.y == y:
                                        lm.blast = ftps() + 1200
                            if Map[y][x].mtype == Soft and prob(50) and EulaDis(t.x, t.y, x, y) <= mods.cfg.TNTPower:
                                Map[y][x].mtype = Void
                                if GB_NOW == GB_LEVEL:
                                    ST_LEVELGOALS.scores[t.belong] += 5
                                draw_block(x, y, Map[y][x].mtype)
                else:
                    for x in range(max(t.x -mods.cfg.TNTPower, 0), min(t.x + mods.cfg.TNTPower + 1, 20)):
                        for y in range(max(t.y -mods.cfg.TNTPower, 0), min(t.y + mods.cfg.TNTPower + 1, 15)):
                            if len(MineList) != 0:
                                for lm in MineList:
                                    if lm.x == x  and lm.y == y:
                                        lm.blast = ftps() + 1200
                            if Map[y][x].mtype == Soft and prob(60):
                                Map[y][x].mtype = Void
                                if GB_NOW == GB_LEVEL:
                                    ST_LEVELGOALS.scores[t.belong] += 5
                                draw_block(x, y, Map[y][x].mtype)
            else:
                #Ellipsis //240105
                pass
            
            # hit entities
            draw_block(t.x, t.y, Map[t.y][t.x].mtype)
            if mods.cfg.UseEulaDistance:
                if EulaDis(t.x, t.y, pl.x, pl.y) <= mods.cfg.TNTPower and pl.health > 0:
                    if t.belong == 0:
                        if mods.cfg.FriendFire == True:
                            pl.health = 0
                            if GB_NOW == GB_LEVEL:
                                ST_LEVELGOALS.scores[0] -= KILLSCORE
                            printm(f'Hit player from ({t.x},{t.y}) when player at ({pl.x},{pl.y}) (TNT, self)')
                    else:
                        pl.health = 0
                        printm(f'Hit player from ({t.x},{t.y}) when player at ({pl.x},{pl.y}) (TNT, other)')
                        if t.belong == 1:
                            e1.kill += 1
                            e1.goal += 1
                            if GB_NOW == GB_LEVEL:
                                if pl.shield == True:
                                    ST_LEVELGOALS.scores[1] += KILLSCORE * 2
                                else:
                                    ST_LEVELGOALS.scores[1] += KILLSCORE * 5
                        elif t.belong == 2:
                            e2.kill += 1
                            e2.goal += 1
                            if GB_NOW == GB_LEVEL:
                                if pl.shield == True:
                                    ST_LEVELGOALS.scores[2] += KILLSCORE * 2
                                else:
                                    ST_LEVELGOALS.scores[2] += KILLSCORE * 5
                        elif t.belong == 3:
                            e3.kill += 1
                            e3.goal += 1
                            if GB_NOW == GB_LEVEL:
                                if pl.shield == True:
                                    ST_LEVELGOALS.scores[3] += KILLSCORE * 2
                                else:
                                    ST_LEVELGOALS.scores[3] += KILLSCORE * 5
                if EulaDis(t.x, t.y, e1.x, e1.y) <= mods.cfg.TNTPower and e1.health > 0:
                    if 1 <= t.belong <= 3:
                        if mods.cfg.FriendFire:
                            e1.health = 0
                            e1.kill += 1
                            e1.goal += 1
                            if GB_NOW == GB_LEVEL:
                                ST_LEVELGOALS.scores[t.belong] += KILLSCORE
                    else:
                        e1.health = 0
                        pl.kill += 1
                        pl.goal += 1
                        if GB_NOW == GB_LEVEL:
                            ST_LEVELGOALS.scores[0] += KILLSCORE
                if EulaDis(t.x, t.y, e2.x, e2.y) <= mods.cfg.TNTPower and e2.health > 0:
                    if 1 <= t.belong <= 3:
                        if mods.cfg.FriendFire:
                            e2.health = 0
                            if GB_NOW == GB_LEVEL:
                                ST_LEVELGOALS.scores[t.belong] += KILLSCORE
                    else:
                        e2.health = 0
                        pl.kill += 1
                        pl.goal += 1
                        if GB_NOW == GB_LEVEL:
                            ST_LEVELGOALS.scores[0] += KILLSCORE
                if EulaDis(t.x, t.y, e3.x, e3.y) <= mods.cfg.TNTPower and e3.health > 0:
                    if 1 <= t.belong <= 3:
                        if mods.cfg.FriendFire:
                            e3.health = 0
                            if GB_NOW == GB_LEVEL:
                                ST_LEVELGOALS.scores[t.belong] += KILLSCORE
                    else:
                        e3.health = 0
                        pl.kill += 1
                        pl.goal += 1
                        if GB_NOW == GB_LEVEL:
                            ST_LEVELGOALS.scores[0] += KILLSCORE
            else:
                # Manhattan distance
                if MhtDis(t.x, t.y, pl.x, pl.y) <= mods.cfg.TNTPower and pl.health > 0:
                    if t.belong == 0:
                        if mods.cfg.FriendFire == True:
                            pl.health = 0
                    else:
                        pl.health = 0
                        if t.belong == 1:
                            e1.kill += 1
                            e1.goal += 1
                            if GB_NOW == GB_LEVEL:
                                ST_LEVELGOALS.scores[1] += KILLSCORE
                        elif t.belong == 2:
                            e2.kill += 1
                            e2.goal += 1
                            if GB_NOW == GB_LEVEL:
                                ST_LEVELGOALS.scores[2] += KILLSCORE
                        elif t.belong == 3:
                            e3.kill += 1
                            e3.goal += 1
                            if GB_NOW == GB_LEVEL:
                                ST_LEVELGOALS.scores[3] += KILLSCORE
                if MhtDis(t.x, t.y, e1.x, e1.y) <= mods.cfg.TNTPower and e1.health > 0:
                    if 1 <= t.belong <= 3:
                        if mods.cfg.FriendFire:
                            e1.health = 0
                            if GB_NOW == GB_LEVEL:
                                ST_LEVELGOALS.scores[t.belong] += KILLSCORE
                    else:
                        e1.health = 0
                        pl.kill += 1
                        pl.goal += 1
                        if GB_NOW == GB_LEVEL:
                            ST_LEVELGOALS.scores[0] += KILLSCORE
                if MhtDis(t.x, t.y, e2.x, e2.y) <= mods.cfg.TNTPower and e2.health > 0:
                    if 1 <= t.belong <= 3:
                        if mods.cfg.FriendFire:
                            e2.health = 0
                            if GB_NOW == GB_LEVEL:
                                ST_LEVELGOALS.scores[t.belong] += KILLSCORE
                    else:
                        e2.health = 0
                        pl.kill += 1
                        pl.goal += 1
                        if GB_NOW == GB_LEVEL:
                            ST_LEVELGOALS.scores[0] += KILLSCORE
                if MhtDis(t.x, t.y, e3.x, e3.y) <= mods.cfg.TNTPower and e3.health > 0:
                    if 1 <= t.belong <= 3:
                        if mods.cfg.FriendFire:
                            e3.health = 0
                            if GB_NOW == GB_LEVEL:
                                ST_LEVELGOALS.scores[t.belong] += KILLSCORE
                    else:
                        e3.health = 0
                        pl.kill += 1
                        pl.goal += 1
                        if GB_NOW == GB_LEVEL:
                            ST_LEVELGOALS.scores[0] += KILLSCORE
        else:
            #t.Time -= 1
            tli.append(t)
    TNTList = tli



@dataclass
class IceThorn: # reach: 3 blocks
    x : int
    y : int
    direct: str
    Time : int  # NOT tps !!! 20tps, [0, 9] (9, 29] [20, 29)  
@dataclass
class IceThornToDraw:
    x : int
    y : int
    state : int = 2
    ID : int = 7

icelist = []
def movIceThorn(entities, isOnlyRender = False):
    global icelist
    if icelist == []:
        return;
    ice_new = []
    for ic in icelist:
        if ic.Time < 39:
            if not isOnlyRender:
                ic.Time += 1
            if ic.direct == 'east':
                xn = ic.x
                for x in range(0, 3):
                    xn = ic.x + x
                    if not 0 <= xn <= 19:
                        break
                    draw_block(xn, ic.y, Map[ic.y][xn].mtype)
                    if ic.Time<= 9:
                        if ic.Time - cint(x*3) >= 2:
                            mods.Iris.draw_item(IceThornToDraw(xn, ic.y, 2, 7))
                        elif ic.Time - cint(x*3) == 1:
                            mods.Iris.draw_item(IceThornToDraw(xn, ic.y, 1, 7))
                        elif ic.Time - cint(x*3) == 0:
                            mods.Iris.draw_item(IceThornToDraw(xn, ic.y, 0, 7))
                        else:
                            pass
                    elif 9 <= ic.Time < 39:
                        mods.Iris.draw_item(IceThornToDraw(xn, ic.y, 2, 7))
                    
                    #if xn > 19 or Map[ic.y][xn] != Void:
                    #    break
                    for en in entities:
                        if en.x == xn and en.y == ic.y:
                            en.frozen = True
            elif ic.direct == 'west':
                xn = ic.x
                for x in range(0, 3):
                    xn = ic.x - x
                    if not 0 <= xn <= 19:
                        break
                    draw_block(xn, ic.y, Map[ic.y][xn].mtype)
                    if ic.Time<= 9:
                        if ic.Time - cint(x*3) >= 2:
                            mods.Iris.draw_item(IceThornToDraw(xn, ic.y, 2, 7))
                        elif ic.Time - cint(x*3) == 1:
                            mods.Iris.draw_item(IceThornToDraw(xn, ic.y, 1, 7))
                        elif ic.Time - cint(x*3) == 0:
                            mods.Iris.draw_item(IceThornToDraw(xn, ic.y, 0, 7))
                        else:
                            pass
                    elif 9 <= ic.Time < 39:
                        mods.Iris.draw_item(IceThornToDraw(xn, ic.y, 2, 7))
                    
                    for en in entities:
                        if en.x == xn and en.y == ic.y:
                            en.frozen = True
            elif ic.direct == 'south':
                yn = ic.y
                for x in range(0, 3):
                    yn = ic.y + x
                    
                    if not 0 <= yn <= 14:
                        break
                    draw_block(ic.x, yn, Map[yn][ic.x].mtype)
                    if ic.Time<= 9:
                        if ic.Time - cint(x*3) >= 2:
                            mods.Iris.draw_item(IceThornToDraw(ic.x, yn, 2, 7))
                        elif ic.Time - cint(x*3) == 1:
                            mods.Iris.draw_item(IceThornToDraw(ic.x, yn, 1, 7))
                        elif ic.Time - cint(x*3) == 0:
                            mods.Iris.draw_item(IceThornToDraw(ic.x, yn, 0, 7))
                        else:
                            pass
                    elif 9 <= ic.Time < 39:
                        mods.Iris.draw_item(IceThornToDraw(ic.x, yn, 2, 7))
                    
                    for en in entities:
                        if en.x == ic.x and en.y == yn:
                            en.frozen = True
            elif ic.direct == 'north':
                yn = ic.y
                for x in range(0, 3):
                    yn = ic.y - x
                    if not 0 <= yn <= 14:
                        break
                    draw_block(ic.x, yn, Map[yn][ic.x].mtype)
                    if ic.Time<= 9:
                        if ic.Time - cint(x*3) >= 2:
                            mods.Iris.draw_item(IceThornToDraw(ic.x, yn, 2, 7))
                        elif ic.Time - cint(x*3) == 1:
                            mods.Iris.draw_item(IceThornToDraw(ic.x, yn, 1, 7))
                        elif ic.Time - cint(x*3) == 0:
                            mods.Iris.draw_item(IceThornToDraw(ic.x, yn, 0, 7))
                        else:
                            pass
                    elif 9 <= ic.Time < 39:
                        mods.Iris.draw_item(IceThornToDraw(ic.x, yn, 2, 7))
                    
                    for en in entities:
                        if en.x == ic.x and en.y == yn:
                            en.frozen = True
            
            ice_new.append(ic)
        else:
            pass
    icelist = ice_new
class DeclEnemy(object):
    __slots__ = 'x', 'y'
    def __init__(self, x, y) -> None:
        self.x : int = x
        self.y : int = y
def cango(who:DeclEnemy, direct):
    if direct == 'east':
        #1.16.5: tighter restrictions
        if 0 <= who.x + 1 < 20 and Map[who.y][who.x+1].mtype == Void and not hasb(who.x+1, who.y):
            return True
        else:
            return False
    elif direct == 'south':
        if 0 <= who.y + 1 < 15 and Map[who.y+1][who.x].mtype == Void and not hasb(who.x, who.y + 1):
            return True
        else:
            return False
    elif direct == 'west':
        if 20 > who.x - 1 >= 0 and Map[who.y][who.x-1].mtype == Void and not hasb(who.x - 1, who.y) :
            return True
        else:
            return False
    elif direct == 'north':
        if 15 > who.y - 1 >= 0 and Map[who.y-1][who.x].mtype == Void and not hasb(who.x, who.y - 1):
            return True
        else:
            return False
    else:
        raise ObsoleteException('cango: direct?')
        print_warning(f'Error direction: {direct}')

def unsafe(who:DeclEnemy, power_Now = 3):
    if hasb(x = who.x, y = who.y):
        return True
    if isUnderThreatOfTNT(who.x, who.y):
        return True
    xn, yn = who.x, who.y
    power = 0
    i = xn
    while i < 19:
        i += 1
        if Map[yn][i].mtype != Void:
            break;
        power += 1
        if hasb(x = i, y = yn) and power_Now > power:
            return True
    
    power = 0
    i = xn
    while i > 0:
        i -= 1
        if Map[yn][i].mtype != Void:
            break;
        power += 1
        if hasb(x = i, y = yn) and power_Now > power:
            return True
    
    power = 0
    i = yn
    while i < 14:
        i += 1
        if Map[i][xn].mtype != Void:
            break;
        power += 1
        if hasb(x = xn, y = i) and power_Now > power:
            return True
    
    power = 0
    i = yn
    while i > 0:
        i -= 1
        if Map[i][xn].mtype != Void:
            break;
        power += 1
        if hasb(x = xn, y = i) and power_Now > power:
            return True
    
    return False



def cango_safe(who, direct, power_Now = 3):
    if not cango(who, direct):
        return False
    xn, yn = 0, 0 # x, y new
    if direct == 'east':
        xn = who.x + 1
        yn = who.y
    elif direct == 'south':
        xn = who.x
        yn = who.y + 1
    elif direct == 'west':
        xn = who.x - 1
        yn = who.y
    elif direct == 'north':
        xn = who.x
        yn = who.y - 1
    power = 0
    i = xn
    while i < 19:
        i += 1
        if Map[yn][i].mtype != Void:
            break;
        power += 1
        if hasb(x = i, y = yn) and power_Now > power:
            return False
    
    power = 0
    i = xn
    while i > 0:
        i -= 1
        if Map[yn][i].mtype != Void:
            break;
        power += 1
        if hasb(x = i, y = yn) and power_Now > power:
            return False
    
    power = 0
    i = yn
    while i < 14:
        i += 1
        if Map[i][xn].mtype != Void:
            break;
        power += 1
        if hasb(x = xn, y = i) and power_Now > power:
            return False
    
    power = 0
    i = yn
    while i > 0:
        i -= 1
        if Map[i][xn].mtype != Void:
            break;
        power += 1
        if hasb(x = xn, y = i) and power_Now > power:
            return False
    
    return True

# 1.16.5
def isUnderThreatOfTNT(x: int, y: int):
    for t in TNTList:
        if EulaDis(t.x, t.y, x, y) <= mods.cfg.TNTPower:
            return True
    return False
# 1.14.1
def issafe(xn: int, yn: int, power_Now = 3):
    if hasb(x = xn, y = yn):
        return False
    # 1.16.5
    if isUnderThreatOfTNT(xn, yn):
        return False
    power = 0
    i = xn
    while i < 19:
        i += 1
        if Map[yn][i].mtype != Void:
            break;
        power += 1
        if hasb(x = i, y = yn) and power_Now > power:
            return False
    
    power = 0
    i = xn
    while i > 0:
        i -= 1
        if Map[yn][i].mtype != Void:
            break;
        power += 1
        if hasb(x = i, y = yn) and power_Now > power:
            return False
    
    power = 0
    i = yn
    while i < 14:
        i += 1
        if Map[i][xn].mtype != Void:
            break;
        power += 1
        if hasb(x = xn, y = i) and power_Now > power:
            return False
    
    power = 0
    i = yn
    while i > 0:
        i -= 1
        if Map[i][xn].mtype != Void:
            break;
        power += 1
        if hasb(x = xn, y = i) and power_Now > power:
            return False
    
    return True

# 1.14
# 1.16.4
class MapVisitSet(object):
    def __init__(self) -> None:
        self.arr: list[list[bool]] = [[False for i in range(15)] for j in range(20)]
    def visit(self, x: int, y: int):
        try:
            self.arr[x][y] = True
        except Exception:
            raise Exception(f'visit ({x}, {y}): {traceback.format_exc()}')
    def isvisited(self, x: int, y: int):
        return self.arr[x][y] == True
def BFSafeBlock(I: DeclEnemy, powermaxnow):
    q = Queue()
    #q.put(Coordinate2i(I.x, I.y))
    visited = MapVisitSet()
    visited.visit(I.x, I.y)
    if cango(I, 'east'):
        q.put(Coordinate2i(I.x + 1, I.y, 'east'))
        visited.visit(I.x + 1, I.y)
    if cango(I, 'west'):
        q.put(Coordinate2i(I.x - 1, I.y, 'west'))
        visited.visit(I.x - 1, I.y)
    if cango(I, 'north'):
        q.put(Coordinate2i(I.x, I.y - 1, 'north'))
        visited.visit(I.x, I.y - 1)
    if cango(I, 'south'):
        q.put(Coordinate2i(I.x, I.y + 1, 'south'))
        visited.visit(I.x, I.y + 1)
    #i = 0
    while not q.empty():
        #i += 1
        #if i > 990:
        #    raise Exception(f'BFSearch depth explode')
        n: Coordinate2i = q.get()
        if issafe(n.x, n.y, powermaxnow):
            return n.state
        else:
            if cango(DeclEnemy(n.x, n.y), 'east') and not visited.isvisited(n.x + 1, n.y) :
                q.put(Coordinate2i(n.x + 1, n.y, n.state))
                visited.visit(n.x + 1, n.y)
            if cango(DeclEnemy(n.x, n.y), 'west') and not visited.isvisited(n.x - 1, n.y):
                q.put(Coordinate2i(n.x - 1, n.y, n.state))
                visited.visit(n.x - 1, n.y)
            if cango(DeclEnemy(n.x, n.y), 'south') and not visited.isvisited(n.x, n.y + 1):
                q.put(Coordinate2i(n.x, n.y + 1, n.state))
                visited.visit(n.x, n.y + 1)
            if cango(DeclEnemy(n.x, n.y), 'north') and not visited.isvisited(n.x, n.y - 1):
                q.put(Coordinate2i(n.x, n.y - 1, n.state))
                visited.visit(n.x, n.y - 1)
    # not find
    return None

class Entity(object):
    
    def __init__(self):
        '''Entity base data, including Player and Enemy.'''
        #position
        self.x  = 0
        self.xp = 0  # 10 max, 1 or 2 add
        self.y  = 0
        self.yp = 0
        
        #properties
        self.power    = 1
        self.capacity = 0
        self.speed    = 2
        self.health   = 1
        self.ctype    = 'entity'
        self.team     = 0

class Player(object):
    __slots__ = (
        'x', 'y', 'xp', 'yp', 'health', 'speed', 'ID', 'nextTps', 'rebirth', 'using_b', 'capacity',
        'bbtime', 'kill', 'died', 'goal', 'ender_pearl_timeout', 'tnt_timeout', 'ice_timeout', 'facing',
        'frozen', 'slow', 'shield', 'hold_tnt', 'power', 'rocket', 'HasAlreadyDied', 'rocket_timeout', 'hold_mine'
    )
    def __init__(self, ID: int = 0):
        self.x = 16
        self.y = 11
        self.xp = 0
        self.yp = 0
        self.health = 1
        self.speed = mods.cfg.player_speed()
        self.ID = ID
        
        self.nextTps = 0
        self.rebirth = -1
        
        #self.bx = 0
        #self.by = 0
        self.using_b = False
        self.capacity = 1 if mods.cfg.DoCapacityIncrease else 3
        self.bbtime = 24
        
        self.kill = 0
        self.died = 0
        self.goal = 0
        self.ender_pearl_timeout = 0
        self.tnt_timeout = 0
        self.ice_timeout = 0
        self.facing = 'west'
        
        self.frozen = False
        self.slow   = -1
        self.shield = False
        
        # 20240103
        # Normal
        self.hold_tnt : int = 0
        self.power : int = 2
        self.rocket : int = 0
        self.rocket_timeout : int = 0
        self.hold_mine : int = 0
        # Double
        self.HasAlreadyDied : bool = False
    
    def mov_effect(self):
        if self.frozen == True:
            self.frozen = False
            self.slow = ftps() + 15_000
            self.speed = cint(mods.cfg.player_speed() / 3)
        if self.slow >= 0:
            if ftps() >= self.slow:
                self.slow = -1
                self.speed = cint(mods.cfg.player_speed())
            else:
                pass

class Enemy(object):
    
    #__slots__ = (
    #    'x', 'y', 'xp', 'yp', 'health', 'speed', 'ID', 'nextTps', 'rebirth', 'using_b', 'capacity',
    #    'bbtime', 'kill', 'died', 'goal', 'ender_pearl_timeout', 'tnt_timeout', 'ice_timeout', 'direct_last',
    #    'frozen', 'slow', 'shield', 'bedx', 'bedy'
    #)
    
    def __init__(self, ID):
        self.x : int = 3 if not mods.cfg.DoEnemyDisperse else 3 if ID in (1, 3) else 16
        self.y : int = 3 if not mods.cfg.DoEnemyDisperse else 3 if ID in (1, 2) else 11
        self.xp: int = 0
        self.yp: int = 0
        self.health = 1
        self.speed = mods.cfg.enemy_speed_1()
        self.ID = ID
        
        self.direct_last = 'east'
        self.nextTps = 0
        self.rebirth = -1
        
        #self.bx = 0
        #self.by = 0
        self.using_b : bool = False
        self.capacity : int = 1 if mods.cfg.DoCapacityIncrease else 3
        self.bbtime : int = 24
        
        self.bedx : int = 3 if self.ID in (1, 3) else 16
        self.bedy : int = 3 if self.ID in (1, 2) else 11
        
        self.kill : int = 0
        self.died : int = 0
        self.goal : int = 0
        
        self.frozen : bool = False
        self.slow : int  = -1
        self.shield : bool = False

        self.ender_pearl_timeout : int = 0
        self.tnt_timeout : int = 0
        
        # Normal
        self.hold_tnt : int = 0
        self.rocket_timeout: int = 0
        self.power : int = 2
        self.rocket : int = 0
        self.hold_mine : int = 0
    
    def mov_effect(self):
        if self.frozen == True:
            self.frozen = False
            self.slow = ftps() + 15_000
            self.speed = cint(mods.cfg.enemy_speed_1() / 3)
        if self.slow >= 0:
            if ftps() >= self.slow:
                self.slow = -1
                self.speed = cint(mods.cfg.enemy_speed_1())
            else:
                pass
        
    def run(self, pl, power_max, e01, e02):
        if GM_NOW == GM_NORMAL:
            self.run_normal(pl, e01, power_max)
            if self.health > 0:
                self.using_rocket(pl, e01)
                self.using_mine()
            return
        global Map
        self.nextTps = ftps() + 50
        if self.health <= 0:
            if self.rebirth < 0:
                self.rebirth = 60 if self.shield == False else 1
                self.died += 1
            #if self.using_b == True:
            #    Map[self.by][self.bx] = Void
            if Map[3][3].mtype != Bed and not self.shield:
                Ellipsis
            else:
                if self.rebirth < 0:
                    self.rebirth = 60
                    self.died += 1
                elif self.rebirth == 0:
                    self.health = 1
                    self.x = 3 if not mods.cfg.DoEnemyDisperse else self.bedx
                    self.y = 3 if not mods.cfg.DoEnemyDisperse else self.bedy
                    self.xp = 0
                    self.yp = 0
                    self.rebirth = -1
                    self.using_b = False
                    if not self.shield:
                        self.goal = 0
                        self.frozen = False
                        self.slow = -1
                        self.speed = mods.cfg.enemy_speed_1()
                    else:
                        self.shield = False
                else:
                    self.rebirth -= 1
            return
        
        if self.frozen == True:
            self.mov_effect()
            return
        else:
            self.mov_effect()
        if self.ender_pearl_timeout > 0:
            self.ender_pearl_timeout -= 1
        
        #if self.xp != 0 or self.yp != 0:
        #    for x in range(max(pl.x-1, 0), min(pl.x+2, 20)):
        #        for y in range(max(pl.y-1, 0), min(pl.y+2, 15)):
        #            draw_block(x, y, Map[y][x])
        
        if self.x > 19:
            self.x = 19
            mods.CrystalLib.print_warning(f'enemy {self.ID} out of range (in Map direction x)')
        if self.y > 14:
            self.y = 14
            print_warning(f'enemy {self.ID} out of range (in Map direction y)')
        if self.xp > 0:
            self.xp += self.speed
            if self.xp > 60:
                self.xp = 0
                self.x += 1
        elif self.yp > 0:
            self.yp += self.speed
            if self.yp > 60:
                self.yp = 0
                self.y += 1
        elif self.xp < 0:
            self.xp -= self.speed
            if self.xp < -60:
                self.xp = 0
                self.x -= 1
        elif self.yp < 0:
            self.yp -= self.speed
            if self.yp < -60:
                self.yp = 0
                self.y -= 1
        else:
            if mods.cfg.FriendFire == True:
                if unsafe(self, power_max):
                    try_times = 12 # 立即移动
                    while try_times > 0:
                        try_times -= 1
                        if prob(25) and cango(self, 'east'):
                            self.xp = 1
                            self.direct_last = 'east'
                            try_times = -1
                        elif prob(25) and cango(self, 'north'):
                            self.yp = -1
                            self.direct_last = 'north'
                            try_times = -1
                        elif prob(25) and cango(self, 'west'):
                            self.xp = -1
                            self.direct_last = 'west'
                            try_times = -1
                        elif cango(self, 'south'):
                            self.yp = 1
                            self.direct_last = 'south'
                            try_times = -1
                    
                elif prob(50):
                    # continue last direct
                    if self.direct_last == 'east' and cango_safe(self, 'east', power_max):
                        self.xp = 1
                    elif self.direct_last == 'west' and cango_safe(self, 'west', power_max):
                        self.xp = -1
                    elif self.direct_last == 'north' and cango_safe(self, 'north', power_max):
                        self.yp = -1
                    elif cango_safe(self, 'south', power_max):
                        self.yp = 1
                elif prob(35):
                    # try to find player
                    if self.x - pl.x > 1 and cango_safe(self, 'west', power_max):
                        self.xp = -1
                    elif pl.x - self.x > 1 and cango_safe(self, 'east', power_max):
                        self.xp = 1
                    elif self.y - pl.y > 1 and cango_safe(self, 'north', power_max):
                        self.yp = -1
                    elif pl.y - self.y > 1 and cango_safe(self, 'south', power_max):
                        self.yp = 1
                    else:
                        if prob(25) and cango_safe(self, 'east', power_max):
                            self.xp = 1
                            self.direct_last = 'east'
                        elif prob(25) and cango_safe(self, 'north', power_max):
                            self.yp = -1
                            self.direct_last = 'north'
                        elif prob(25) and cango_safe(self, 'west', power_max):
                            self.xp = -1
                            self.direct_last = 'west'
                        elif cango_safe(self, 'south', power_max):
                            self.yp = 1
                            self.direct_last = 'south'
                else:
                    if prob(25) and cango_safe(self, 'east', power_max):
                        self.xp = 1
                        self.direct_last = 'east'
                    elif prob(25) and cango_safe(self, 'north', power_max):
                        self.yp = -1
                        self.direct_last = 'north'
                    elif prob(25) and cango_safe(self, 'west', power_max):
                        self.xp = -1
                        self.direct_last = 'west'
                    elif cango_safe(self, 'south', power_max):
                        self.yp = 1
                        self.direct_last = 'south'
        
            else:
                # No FriendFire
                if prob(50):
                    # continue last direct
                    if self.direct_last == 'east' and cango(self, 'east'):
                        self.xp = 1
                    elif self.direct_last == 'west' and cango(self, 'west'):
                        self.xp = -1
                    elif self.direct_last == 'north' and cango(self, 'north'):
                        self.yp = -1
                    elif cango(self, 'south'):
                        self.yp = 1
                elif prob(35):
                    # try to find player
                    if self.x - pl.x > 1 and cango(self, 'west'):
                        self.xp = -1
                    elif pl.x - self.x > 1 and cango(self, 'east'):
                        self.xp = 1
                    elif self.y - pl.y > 1 and cango(self, 'north'):
                        self.yp = -1
                    elif pl.y - self.y > 1 and cango(self, 'south'):
                        self.yp = 1
                    else:
                        if prob(25) and cango(self, 'east'):
                            self.xp = 1
                            self.direct_last = 'east'
                        elif prob(25) and cango(self, 'north'):
                            self.yp = -1
                            self.direct_last = 'north'
                        elif prob(25) and cango(self, 'west'):
                            self.xp = -1
                            self.direct_last = 'west'
                        elif cango(self, 'south'):
                            self.yp = 1
                            self.direct_last = 'south'
                else:
                    if prob(25) and cango(self, 'east'):
                        self.xp = 1
                        self.direct_last = 'east'
                    elif prob(25) and cango(self, 'north'):
                        self.yp = -1
                        self.direct_last = 'north'
                    elif prob(25) and cango(self, 'west'):
                        self.xp = -1
                        self.direct_last = 'west'
                    elif cango(self, 'south'):
                        self.yp = 1
                        self.direct_last = 'south'
        
        if self.goal >= 3:
            # TNT
            if abs(pl.y - self.y) < 5:
                if 0 < pl.x - self.x <= mods.cfg.TNTPower+1: # east
                    Create_TNT(TNT(
                        x = min(self.x + mods.cfg.TNTPower+1, 19), y = self.y, belong = self.ID,Time=ftps()+1_600,
                        xFrom=self.x, yFrom=self.y
                    ))
                    self.goal -= 3
                elif 0 < self.x - pl.x <= mods.cfg.TNTPower+1: # west
                    Create_TNT(TNT(
                        x = max(self.x - mods.cfg.TNTPower-1, 0), y = self.y, belong = self.ID,Time=ftps()+1_600,
                        xFrom=self.x, yFrom=self.y
                    ))
                    self.goal -= 3
            elif abs(pl.x - self.x) < 5:
                if 0 < pl.y - self.y <= mods.cfg.TNTPower+1: # south
                    Create_TNT(TNT(
                        y = min(self.y + mods.cfg.TNTPower+1, 14), x = self.x, belong = self.ID,Time=ftps()+1_600,
                        xFrom=self.x, yFrom=self.y
                    ))
                    self.goal -= 3
                elif 0 < self.y - pl.y <= mods.cfg.TNTPower+1: # north
                    Create_TNT(TNT(
                        y = max(self.y - mods.cfg.TNTPower-1, 0), x = self.x, belong = self.ID,Time=ftps()+1_600,
                        xFrom=self.x, yFrom=self.y
                    ))
                    self.goal -= 3
                
        if self.goal >= 2 and (9<=self.x<=12) and (10 <= self.y <= 12):
            self.goal -= 2
            self.ender_pearl_timeout = 200
            self.x += mods.cfg.item_ender_pearl_distance()
            if self.x > 19:
                self.x = 19
        if self.goal >= 2 and 4<=self.y<=8 and (15 <= self.x <= 17):
            self.goal -= 2
            self.ender_pearl_timeout = 200
            self.y += mods.cfg.item_ender_pearl_distance()
            if self.y > 14:
                self.y = 14
        if Map[self.y][self.x].mtype == Void and mods.EntityLib.setblock_case(self.x, self.y):
            Map[self.y][self.x].mtype = Soft
        
        if self.capacity > 0:
            setbomb_prob = 16 if EulaDis(self.x, self.y, pl.x, pl.y) <= power_max else 4
            if prob(setbomb_prob) and not hasb(self.x, self.y) and Map[self.y][self.x].mtype == Void:
                CreateBomb(Bomb(x = self.x, y = self.y, belong = self.ID, power = power_max, Time = ftps() + 2_200,
                                  color = mods.cfg.ask_e_color(self.ID)))
                self.capacity -= 1
        
        # above is olds. //20240103 delete it.
    
    # 2024 01 03
    def run_normal(self, pl:Player, e01: DeclEnemy | Player, power_max):
        global Map
        #power_max = pl.power
        self.nextTps = ftps() + 50
        if self.health <= 0:
            if self.rebirth < 0:
                self.rebirth = 60 if self.shield == False else 1
                self.died += 1
            #if self.using_b == True:
            #    Map[self.by][self.bx] = Void
            if Map[3][3].mtype != Bed and not self.shield:
                Ellipsis
            else:
                if self.rebirth < 0:
                    self.rebirth = 60
                    self.died += 1
                elif self.rebirth == 0:
                    self.health = 1
                    self.x = 3 if not mods.cfg.DoEnemyDisperse else self.bedx
                    self.y = 3 if not mods.cfg.DoEnemyDisperse else self.bedy
                    self.xp = 0
                    self.yp = 0
                    self.rebirth = -1
                    self.using_b = False
                    if not self.shield:
                        self.goal = 0
                        self.frozen = False
                        self.slow = -1
                        self.speed = mods.cfg.enemy_speed_1()
                    else:
                        self.shield = False
                else:
                    self.rebirth -= 1
            return
        
        if self.frozen == True:
            self.mov_effect()
            return
        else:
            self.mov_effect()
        if self.ender_pearl_timeout > 0:
            self.ender_pearl_timeout -= 1
        if self.tnt_timeout > 0:
            self.tnt_timeout -= 1
        #if self.xp != 0 or self.yp != 0:
        #    for x in range(max(pl.x-1, 0), min(pl.x+2, 20)):
        #        for y in range(max(pl.y-1, 0), min(pl.y+2, 15)):
        #            draw_block(x, y, Map[y][x])
        
        match Map[self.y][self.x].addon:
            case mods.Iris.MA_NULL:
                pass
            case mods.Iris.MA_APPLE:
                self.capacity += 1
                Map[self.y][self.x].addon = mods.Iris.MA_NULL
                if GB_NOW == GB_LEVEL:
                    ST_LEVELGOALS.scores[self.ID] += 20
            case mods.Iris.MA_POWER:
                self.power += 1
                Map[self.y][self.x].addon = mods.Iris.MA_NULL
                if GB_NOW == GB_LEVEL:
                    ST_LEVELGOALS.scores[self.ID] += 20
            case mods.Iris.MA_TNT:
                self.hold_tnt += 1
                Map[self.y][self.x].addon = mods.Iris.MA_NULL
                if GB_NOW == GB_LEVEL:
                    ST_LEVELGOALS.scores[self.ID] += 20
            case mods.Iris.MA_SHIELD:
                self.shield = True
                Map[self.y][self.x].addon = mods.Iris.MA_NULL
                if GB_NOW == GB_LEVEL:
                    ST_LEVELGOALS.scores[self.ID] += 20
            case mods.Iris.MA_ROCKET:
                self.rocket += 1
                Map[self.y][self.x].addon = mods.Iris.MA_NULL
                if GB_NOW == GB_LEVEL:
                    ST_LEVELGOALS.scores[self.ID] += 20
            case mods.Iris.MA_DIAMOND:
                if GB_NOW == GB_LEVEL:
                    ST_LEVELGOALS.scores[self.ID] += 500
                else:
                    pass
                Map[self.y][self.x].addon = mods.Iris.MA_NULL
            case mods.Iris.MA_LANDMINE:
                self.hold_mine += 1
                Map[self.y][self.x].addon = mods.Iris.MA_NULL
                if GB_NOW == GB_LEVEL:
                    ST_LEVELGOALS.scores[self.ID] += 20
            case default:
                pass
        if self.x > 19:
            self.x = 19
            mods.CrystalLib.print_warning(f'enemy {self.ID} out of range (in Map direction x)')
        if self.y > 14:
            self.y = 14
            print_warning(f'enemy {self.ID} out of range (in Map direction y)')
        if self.xp > 0:
            self.xp += self.speed
            if self.xp > 60:
                self.xp = 0
                self.x += 1
        elif self.yp > 0:
            self.yp += self.speed
            if self.yp > 60:
                self.yp = 0
                self.y += 1
        elif self.xp < 0:
            self.xp -= self.speed
            if self.xp < -60:
                self.xp = 0
                self.x -= 1
        elif self.yp < 0:
            self.yp -= self.speed
            if self.yp < -60:
                self.yp = 0
                self.y -= 1
        else:
            if True or mods.cfg.FriendFire == True:
                # 241028
                if unsafe(self, power_max):
                    direction = BFSafeBlock(self, power_max)
                    #match direction:
                    if direction == 'east':
                        self.xp = 1
                        self.direct_last = 'east'
                    elif direction == 'west':
                        self.xp = -1
                        self.direct_last = 'west'
                    elif direction == 'north':
                        self.yp = -1
                        self.direct_last = 'north'
                    elif direction == 'south':
                        self.yp = 1
                        self.direct_last = 'south'
                    else:
                        # None
                        try_times = 12 # 立即移动
                        while try_times > 0:
                            try_times -= 1
                            if prob(25) and cango(self, 'east'):
                                self.xp = 1
                                self.direct_last = 'east'
                                try_times = -1
                                break #240104
                            elif prob(25) and cango(self, 'north'):
                                self.yp = -1
                                self.direct_last = 'north'
                                try_times = -1
                                break # 240104
                            elif prob(25+8) and cango(self, 'west'):
                                self.xp = -1
                                self.direct_last = 'west'
                                try_times = -1
                                break
                            elif cango(self, 'south'):
                                self.yp = 1
                                self.direct_last = 'south'
                                try_times = -1
                                break
                elif False:
                    raise ObsoleteException('!!')
                    incase : bool = False
                    try_times = 24 # safer
                    while try_times > 0: # 240104 add
                        try_times -= 1
                        # 20240105
                        # fix decl:
                        # cango_safe -> cango, as 'safe' can never be acquire
                        if prob(25) and (cango(self, 'east') if hasb(self.x, self.y) else cango_safe(self, 'east', power_max)) and IsInBarrier(self.x+1,self.y,self.x,self.y)==False:
                            self.xp = 1
                            self.direct_last = 'east'
                            try_times = -1
                            incase = True
                            break
                        elif prob(25+8) and (cango(self, 'north') if hasb(self.x, self.y) else cango_safe(self, 'north', power_max)) and IsInBarrier(self.x,self.y-1,self.x,self.y)==False:
                            self.yp = -1
                            self.direct_last = 'north'
                            try_times = -1
                            incase = True
                            break
                        elif prob(25+25) and (cango(self, 'west')  if hasb(self.x, self.y) else cango_safe(self, 'west', power_max)) and IsInBarrier(self.x-1,self.y,self.x,self.y)==False:
                            self.xp = -1
                            self.direct_last = 'west'
                            try_times = -1
                            incase = True
                            break
                        elif (cango(self, 'south')  if hasb(self.x, self.y) else cango_safe(self, 'south', power_max)) and IsInBarrier(self.x,self.y+1,self.x,self.y)==False:
                            self.yp = 1
                            self.direct_last = 'south'
                            try_times = -1
                            incase = True
                            break
                    if incase == False:
                        try_times = 12 # 立即移动
                        while try_times > 0:
                            try_times -= 1
                            if prob(25) and cango(self, 'east'):
                                self.xp = 1
                                self.direct_last = 'east'
                                try_times = -1
                                break #240104
                            elif prob(25) and cango(self, 'north'):
                                self.yp = -1
                                self.direct_last = 'north'
                                try_times = -1
                                break # 240104
                            elif prob(25+8) and cango(self, 'west'):
                                self.xp = -1
                                self.direct_last = 'west'
                                try_times = -1
                                break
                            elif cango(self, 'south'):
                                self.yp = 1
                                self.direct_last = 'south'
                                try_times = -1
                                break
                    
                elif prob(50):
                    # continue last direct
                    if self.direct_last == 'east' and cango_safe(self, 'east', power_max):# and IsInBarrier(self.x+1,self.y,self.x, self.y) == False:
                        self.xp = 1
                    elif self.direct_last == 'west' and cango_safe(self, 'west', power_max):# and IsInBarrier(self.x-1,self.y,self.x, self.y) == False:
                        self.xp = -1
                    elif self.direct_last == 'north' and cango_safe(self, 'north', power_max):# and IsInBarrier(self.x,self.y-1,self.x, self.y) == False:
                        self.yp = -1
                    elif cango_safe(self, 'south', power_max):# and IsInBarrier(self.x,self.y+1,self.x, self.y) == False:
                        self.yp = 1
                elif prob(35):
                    # try to find player
                    if self.x - pl.x > 1 and cango_safe(self, 'west', power_max):# and IsInBarrier(self.x-1,self.y,self.x, self.y) == False:
                        self.xp = -1
                    elif pl.x - self.x > 1 and cango_safe(self, 'east', power_max):# and IsInBarrier(self.x+1,self.y,self.x, self.y) == False:
                        self.xp = 1
                    elif self.y - pl.y > 1 and cango_safe(self, 'north', power_max):# and IsInBarrier(self.x,self.y-1,self.x, self.y) == False:
                        self.yp = -1
                    elif pl.y - self.y > 1 and cango_safe(self, 'south', power_max):# and IsInBarrier(self.x,self.y+1,self.x, self.y) == False:
                        self.yp = 1
                    else:
                        if prob(25) and cango_safe(self, 'east', power_max):# and IsInBarrier(self.x+1,self.y,self.x, self.y) == False:
                            self.xp = 1
                            self.direct_last = 'east'
                        elif prob(25+8) and cango_safe(self, 'north', power_max):# and IsInBarrier(self.x,self.y-1,self.x, self.y) == False:
                            self.yp = -1
                            self.direct_last = 'north'
                        elif prob(25+25) and cango_safe(self, 'west', power_max):# and IsInBarrier(self.x-1,self.y,self.x, self.y) == False:
                            self.xp = -1
                            self.direct_last = 'west'
                        elif cango_safe(self, 'south', power_max):# and IsInBarrier(self.x,self.y+1,self.x, self.y) == False:
                            self.yp = 1
                            self.direct_last = 'south'
                else:
                    if prob(25) and cango_safe(self, 'east', power_max):# and IsInBarrier(self.x+1,self.y,self.x, self.y) == False:
                        self.xp = 1
                        self.direct_last = 'east'
                    elif prob(25) and cango_safe(self, 'north', power_max):# and IsInBarrier(self.x,self.y-1,self.x, self.y) == False:
                        self.yp = -1
                        self.direct_last = 'north'
                    elif prob(25) and cango_safe(self, 'west', power_max):# and IsInBarrier(self.x-1,self.y,self.x, self.y) == False:
                        self.xp = -1
                        self.direct_last = 'west'
                    elif cango_safe(self, 'south', power_max):# and IsInBarrier(self.x,self.y+1,self.x, self.y) == False:
                        self.yp = 1
                        self.direct_last = 'south'
        
            else:
                # No FriendFire
                if prob(50):
                    # continue last direct
                    if self.direct_last == 'east' and cango(self, 'east'):
                        self.xp = 1
                    elif self.direct_last == 'west' and cango(self, 'west'):
                        self.xp = -1
                    elif self.direct_last == 'north' and cango(self, 'north'):
                        self.yp = -1
                    elif cango(self, 'south'):
                        self.yp = 1
                elif prob(35):
                    # try to find player
                    if self.x - pl.x > 1 and cango(self, 'west'):
                        self.xp = -1
                    elif pl.x - self.x > 1 and cango(self, 'east'):
                        self.xp = 1
                    elif self.y - pl.y > 1 and cango(self, 'north'):
                        self.yp = -1
                    elif pl.y - self.y > 1 and cango(self, 'south'):
                        self.yp = 1
                    else:
                        if prob(25) and cango(self, 'east'):
                            self.xp = 1
                            self.direct_last = 'east'
                        elif prob(25) and cango(self, 'north'):
                            self.yp = -1
                            self.direct_last = 'north'
                        elif prob(25) and cango(self, 'west'):
                            self.xp = -1
                            self.direct_last = 'west'
                        elif cango(self, 'south'):
                            self.yp = 1
                            self.direct_last = 'south'
                else:
                    if prob(25) and cango(self, 'east'):
                        self.xp = 1
                        self.direct_last = 'east'
                    elif prob(25) and cango(self, 'north'):
                        self.yp = -1
                        self.direct_last = 'north'
                    elif prob(25) and cango(self, 'west'):
                        self.xp = -1
                        self.direct_last = 'west'
                    elif cango(self, 'south'):
                        self.yp = 1
                        self.direct_last = 'south'
        
        if self.hold_tnt > 0 and self.tnt_timeout <= 0:
            # TNT
            if abs(pl.y - self.y) < 5:
                if 0 < pl.x - self.x <= mods.cfg.TNTPower +2: # east
                    Create_TNT(TNT(
                        x = min(self.x + mods.cfg.TNTPower + 3, 19), y = self.y, belong = self.ID,Time=ftps()+1_600,
                        xFrom=self.x, yFrom=self.y
                    ))
                    self.hold_tnt -= 1
                    self.tnt_timeout = 18
                elif 0 < self.x - pl.x <= mods.cfg.TNTPower +2: # west
                    Create_TNT(TNT(
                        x = max(self.x - mods.cfg.TNTPower - 3, 0), y = self.y, belong = self.ID,Time=ftps()+1_600,
                        xFrom=self.x, yFrom=self.y
                    ))
                    self.hold_tnt -= 1
                    self.tnt_timeout = 18
            elif abs(pl.x - self.x) < 5:
                if 0 < pl.y - self.y <= mods.cfg.TNTPower +2: # south
                    Create_TNT(TNT(
                        y = min(self.y + mods.cfg.TNTPower + 3, 14), x = self.x, belong = self.ID,Time=ftps()+1_600,
                        xFrom=self.x, yFrom=self.y
                    ))
                    self.hold_tnt -= 1
                    self.tnt_timeout = 18
                elif 0 < self.y - pl.y <= mods.cfg.TNTPower +2: # north
                    Create_TNT(TNT(
                        y = max(self.y - mods.cfg.TNTPower - 3, 0), x = self.x, belong = self.ID,Time=ftps()+1_600,
                        xFrom=self.x, yFrom=self.y
                    ))
                    self.hold_tnt -= 1
                    self.tnt_timeout = 18
        if type(e01) == Player and self.hold_tnt > 0 and self.tnt_timeout <= 0:
            if abs(e01.y - self.y) < 5:
                if 0 < e01.x - self.x <= mods.cfg.TNTPower +2: # east
                    Create_TNT(TNT(
                        x = min(self.x + mods.cfg.TNTPower + 3, 19), y = self.y, belong = self.ID,Time=ftps()+1_600,
                        xFrom=self.x, yFrom=self.y
                    ))
                    self.hold_tnt -= 1
                    self.tnt_timeout = 18
                elif 0 < self.x - e01.x <= mods.cfg.TNTPower +2: # west
                    Create_TNT(TNT(
                        x = max(self.x - mods.cfg.TNTPower - 3, 0), y = self.y, belong = self.ID,Time=ftps()+1_600,
                        xFrom=self.x, yFrom=self.y
                    ))
                    self.hold_tnt -= 1
                    self.tnt_timeout = 18
            elif abs(e01.x - self.x) < 5:
                if 0 < e01.y - self.y <= mods.cfg.TNTPower +2: # south
                    Create_TNT(TNT(
                        y = min(self.y + mods.cfg.TNTPower + 3, 14), x = self.x, belong = self.ID,Time=ftps()+1_600,
                        xFrom=self.x, yFrom=self.y
                    ))
                    self.hold_tnt -= 1
                    self.tnt_timeout = 18
                elif 0 < self.y - e01.y <= mods.cfg.TNTPower +2: # north
                    Create_TNT(TNT(
                        y = max(self.y - mods.cfg.TNTPower - 3, 0), x = self.x, belong = self.ID,Time=ftps()+1_600,
                        xFrom=self.x, yFrom=self.y
                    ))
                    self.hold_tnt -= 1
                    self.tnt_timeout = 18
        if False and self.goal >= 2 and (9<=self.x<=12) and (10 <= self.y <= 12):
            self.goal -= 2
            self.ender_pearl_timeout = 200
            self.x += mods.cfg.item_ender_pearl_distance()
            if self.x > 19:
                self.x = 19
        if False and self.goal >= 2 and 4<=self.y<=8 and (15 <= self.x <= 17):
            self.goal -= 2
            self.ender_pearl_timeout = 200
            self.y += mods.cfg.item_ender_pearl_distance()
            if self.y > 14:
                self.y = 14
        ''' No Bed Mode !!! '''
        #if Map[self.y][self.x].mtype == Void and mods.EntityLib.setblock_case(self.x, self.y):
        #    Map[self.y][self.x].mtype = Soft
        
        if self.capacity > 0:
            setbomb_prob = 16 if EulaDis(self.x, self.y, pl.x, pl.y) <= power_max else 4
            if prob(setbomb_prob) and not hasb(self.x, self.y) and Map[self.y][self.x].mtype == Void and Enemy_CanPlaceBomb(self)\
                and self.xp == 0 and self.yp == 0:
                CreateBomb(Bomb(x = self.x, y = self.y, belong = self.ID, power = self.power, Time = ftps() + 2_200,
                                  color = mods.cfg.ask_e_color(self.ID)))
                self.capacity -= 1
    def using_rocket(self, pl:Player, pl2: Player|None = None):
        if self.rocket_timeout > 0:
            self.rocket_timeout -= 1
            return
        if self.rocket > 0:
            EnemyRocketTryToShootPlayer(self, pl)
        else:
            # no rocket
            pass
        if self.rocket > 0 and pl2 != None:
            EnemyRocketTryToShootPlayer(self, pl2)
        else:
            # no rocket
            pass
    def using_mine(self ):
        if self.hold_mine > 0 and prob(2):
            Try_SetLandMine(self)
    
# 20240103
def IsInBarrier(x: int, y: int, xFrom: int, yFrom: int):
    if x-1 < 0:
        pass
    else:
        if Map[y][x-1].mtype == Void and (x-1 != xFrom):
            return False
    if x+1 >= 20:
        pass
    else:
        if Map[y][x+1].mtype == Void and (x+1 != xFrom):
            return False
    if y - 1 < 0:
        pass
    else:
        if Map[y-1][x].mtype == Void and (y-1 != yFrom):
            return False
    if y + 1 >= 15:
        pass
    else:
        if Map[y+1][x].mtype == Void and (y+1 != yFrom):
            return False
    return True
def Enemy_CanPlaceBomb(e: Enemy):
    if e.x - 1 < 0:
        pass
    else:
        if IsInBarrier(e.x - 1, e.y, e.x, e.y) == False and unsafe(DeclEnemy(e.x - 1, e.y)) == False:
            return True
    if e.x + 1 >= 20:
        pass
    else:
        if IsInBarrier(e.x + 1, e.y, e.x, e.y) == False and unsafe(DeclEnemy(e.x+1, e.y)) == False:
            return True
    if e.y - 1 < 0:
        pass
    else:
        if IsInBarrier(e.x, e.y - 1, e.x, e.y) == False and unsafe(DeclEnemy(e.x, e.y-1)) == False:
            return True
    if e.y + 1 >= 15:
        pass
    else:
        if IsInBarrier(e.x, e.y + 1, e.x, e.y) == False and unsafe(DeclEnemy(e.x, e.y+1))==False:
            return True
    return False

# 240105
def EnemyRocketTryToShootPlayer(self : Enemy, pl: Player) -> None:
    i=0
    if self.x == pl.x:
        if self.y > pl.y:
            i = self.y
            while i > pl.y:
                i -= 1
                if Map[i][self.x].mtype in (Soft, Hard):
                    return
            AddRocket(self.x, self.y, 'north', self.ID)
            self.rocket -= 1
            self.rocket_timeout = 16 # 1.16.2
        else:
            i = self.y
            while i < pl.y:
                i += 1
                if Map[i][self.x].mtype in (Soft, Hard):
                    return
            AddRocket(self.x, self.y, 'south', self.ID)
            self.rocket -= 1
            self.rocket_timeout = 16 # 1.16.2
    elif self.y == pl.y:
        if self.x > pl.x:
            i = self.x
            while i > pl.x:
                i -= 1
                if Map[self.y][i].mtype in (Soft, Hard):
                    return
            AddRocket(self.x, self.y, 'west', self.ID)
            self.rocket -= 1
            self.rocket_timeout = 16 # 1.16.2
        else:
            i = self.x
            while i < pl.x:
                i += 1
                if Map[self.y][i].mtype in (Soft, Hard):
                    return
            AddRocket(self.x, self.y, 'east', self.ID)
            self.rocket -= 1
            self.rocket_timeout = 16 # 1.16.2

# 240104

# tps = 1000/20 = 50, speed=50*15 = 750 p/s
def MovRocket(r : Rocket, pl: Player, e1: Enemy | Player, e2:Enemy, e3:Enemy):
    match r.direct:
        case 'west':
            r.x -= 15
        case 'east':
            r.x += 15
        case 'south':
            r.y += 15
        case 'north':
            r.y -= 15
        case default:
            pass
    xb: blockpos_t = r.x // 60
    yb: blockpos_t = r.y // 60
    if r.belong != pl.ID and xb == pl.x and yb == pl.y and pl.health > 0:
        pl.health = 0
        printm(f'Hit player from ({xb},{yb}) when player at ({pl.x},{pl.y}) (rocket)')
        r.live = False
        if GB_NOW == GB_LEVEL:
            if pl.shield == True:
                ST_LEVELGOALS.scores[r.belong] += KILLSCORE * 2
            else:
                ST_LEVELGOALS.scores[r.belong] += KILLSCORE * 5
    if r.belong != e1.ID and xb == e1.x and yb == e1.y and e1.health > 0:
        e1.health = 0
        r.live = False
        if GB_NOW == GB_LEVEL:
            ST_LEVELGOALS.scores[r.belong] += KILLSCORE
    if r.belong != e2.ID and  xb == e2.x and yb == e2.y and e2.health > 0:
        e2.health = 0
        r.live = False
        if GB_NOW == GB_LEVEL:
            ST_LEVELGOALS.scores[r.belong] += KILLSCORE
    if r.belong != e3.ID and  xb == e3.x and yb == e3.y and e3.health > 0:
        e3.health = 0
        r.live = False
        if GB_NOW == GB_LEVEL:
            ST_LEVELGOALS.scores[r.belong] += KILLSCORE
    if not (0 <= xb <= 19) or not (0 <= yb <= 14):
        r.live = False
    else:
        match Map[yb][xb].mtype:
            case 0: # void/air
                pass
            case 1: #soft
                Map[yb][xb].mtype = 0
                r.live = False
                if GB_NOW == GB_LEVEL:
                    ST_LEVELGOALS.scores[r.belong] += 5
            case 2:
                r.live = False
            case 3:
                r.live = False
            case default:
                pass

def HaveMine(xb : blockpos_t, yb : blockpos_t) -> bool:
    if len(MineList) == 0:
        return False
    else:
        for lm in MineList:
            if lm.x == xb and lm.y == yb:
                return True
    return False
def Try_SetLandMine(this : Enemy | Player):
    if HaveMine(this.x, this.y) == True:
        pass
    else:
        MineList.append(
            Landmine(
                this.x, this.y, this.ID
            )
        )
        this.hold_mine -= 1

def MoveMine(tup : tuple[Player, Enemy|Player, Enemy, Enemy]):
    global MineList
    if len(MineList) == 0:
        return
    new = []
    tps = ftps()
    for lm in MineList:
        if lm.blast == None:
            for e in tup:
                if e.ID != lm.belong and e.x == lm.x and e.y == lm.y and e.health > 0:
                    e.health = 0
                    tup[lm.belong].kill += 1
                    lm.blast = False
                    if GB_NOW == GB_LEVEL:
                        ST_LEVELGOALS.scores[lm.belong] += KILLSCORE
                    if e.ID == 0:
                        printm(f"Hit player in ({e.x},{e.y}) (Mine, other)")
            new.append(lm)
        elif lm.blast == False:
            lm.blast = tps + 1200
            new.append(lm)
            for e in tup:
                if e.x == lm.x and e.y == lm.y and e.health > 0:
                    e.health = 0
                    tup[lm.belong].kill += 1
                    #lm.blast = tps + 1200
                    if GB_NOW == GB_LEVEL:
                        ST_LEVELGOALS.scores[lm.belong] += KILLSCORE
                    if e.ID == 0:
                        printm(f"Hit player in ({e.x},{e.y}) (Mine, {'self' if e.ID == lm.belong else 'other'})")
        else:
            if lm.blast < tps:
                pass
            else:
                new.append(lm)
    MineList = new

# 2024/01/02
def MovBombNeo(pl: Player, e1: Enemy, e2: Enemy, e3: Enemy):
    global blist
    if len(blist) == 0:
        return
    new : list[Bomb] = []
    eli = (e1, e2, e3)
    Tm = ftps()
    for this in blist:
        if Tm < this.Time:
            mods.Iris.draw_item(this)
            new.append(this)
        elif Tm - this.Time < 9_00:
            if this.HadCalcedRange == False:
                Bomb_CalcRange(this)
                this.HadCalcedRange = True
            colour = (0,0,0)
            if this.belong == 0:
                colour = (0, 230, 252) # player
            elif this.belong == 1:
                colour = (255, 6, 255)
            elif this.belong == 2:
                colour = (0xfd, 0xe3, 0x02)
            elif this.belong == 3:
                colour = (0xc5, 0xfb, 0x46)
            
            mods.Iris.draw_item(bbs(x = this.x, y = this.y, color = colour, ID = 5, towards = 0))
            if ((this.xsub <= pl.x <= this.xadd and this.y == pl.y) or (this.ysub <= pl.y <= this.yadd and this.x == pl.x)):
                if this.belong == 0:
                    if mods.cfg.FriendFire == True and pl.health > 0:
                        pl.health = 0
                        printm(f'Hit player from ({this.x},{this.y}) when player at ({pl.x},{pl.y}) (bomb, self)')
                        if GB_NOW == GB_LEVEL:
                            ST_LEVELGOALS.scores[pl.ID] -= KILLSCORE
                elif pl.health > 0:
                    pl.health = 0
                    eli[this.belong - 1].kill += 1
                    if GB_NOW == GB_LEVEL:
                        if pl.shield == True:
                            ST_LEVELGOALS.scores[eli[this.belong - 1].ID] += KILLSCORE * 2
                        else:
                            ST_LEVELGOALS.scores[eli[this.belong - 1].ID] += KILLSCORE * 5
                    printm(f'Hit player from ({this.x},{this.y}) when player at ({pl.x},{pl.y}) (bomb, other)')
            #eli = (e1, e2, e3)
            for e in eli:
                if ((this.xsub <= e.x <= this.xadd and this.y == e.y) or (this.ysub <= e.y <= this.yadd and this.x == e.x)):
                    if 0 < this.belong :
                        if mods.cfg.FriendFire == True and e.health > 0:
                            e.health = 0
                            eli[this.belong - 1].kill += 1
                            if GB_NOW == GB_LEVEL and this.belong != e.ID:
                                ST_LEVELGOALS.scores[eli[this.belong - 1].ID] += KILLSCORE
                    else:
                        if e.health > 0:
                            e.health = 0
                            pl.kill += 1
                            if GB_NOW == GB_LEVEL:
                                ST_LEVELGOALS.scores[pl.ID] += KILLSCORE
            for x in range(this.xsub, this.xadd+1):
                mods.Iris.draw_item(bbs(x=x, y=this.y, color=colour, ID=5, towards=2))
                if GB_NOW == GB_LEVEL:
                    if len(MineList) == 0:
                        pass
                    else:
                        for lm in MineList:
                            if lm.x == x  and  lm.y == this.y and lm.blast == None:
                                lm.blast = ftps() + 1200
                if Map[this.y][x].mtype in (Bed, Soft):
                    Map[this.y][x].mtype = Void
                    if GB_NOW == GB_LEVEL:
                        ST_LEVELGOALS.scores[this.belong] += 5
                    if GM_NOW == GM_NORMAL:
                        Map[this.y][x].addon = rd.choice(
                            [mods.Iris.MA_NULL]*6 + [mods.Iris.MA_APPLE, mods.Iris.MA_POWER, mods.Iris.MA_TNT, mods.Iris.MA_SHIELD, mods.Iris.MA_ROCKET]
                        ) if GB_NOW == 0 else rd.choice(
                            [mods.Iris.MA_NULL]*6 + [mods.Iris.MA_APPLE, mods.Iris.MA_POWER, mods.Iris.MA_TNT, mods.Iris.MA_SHIELD, mods.Iris.MA_ROCKET, mods.Iris.MA_DIAMOND, mods.Iris.MA_LANDMINE]
                        )
            for x in range(this.ysub, this.yadd+1):
                mods.Iris.draw_item(bbs(x=this.x, y=x, color=colour, ID=5, towards=1))
                if GB_NOW == GB_LEVEL:
                    if len(MineList) == 0:
                        pass
                    else:
                        for lm in MineList:
                            if lm.x == this.x  and  lm.y == x and lm.blast == None:
                                lm.blast = ftps() + 1200
                if Map[x][this.x].mtype in (Soft, Bed):
                    Map[x][this.x].mtype = Void
                    if GB_NOW == GB_LEVEL:
                        ST_LEVELGOALS.scores[this.belong] += 5
                    if GM_NOW == GM_NORMAL:
                        Map[x][this.x].addon = rd.choice(
                            [mods.Iris.MA_NULL]*6 + [mods.Iris.MA_APPLE, mods.Iris.MA_POWER, mods.Iris.MA_TNT, mods.Iris.MA_SHIELD, mods.Iris.MA_ROCKET]
                        ) if GB_NOW == 0 else rd.choice(
                            [mods.Iris.MA_NULL]*6 + [mods.Iris.MA_APPLE, mods.Iris.MA_POWER, mods.Iris.MA_TNT, mods.Iris.MA_SHIELD, mods.Iris.MA_ROCKET, mods.Iris.MA_DIAMOND, mods.Iris.MA_LANDMINE]
                        )
            new.append(this)
        else:
            if this.belong == 0:
                pl.capacity += 1
            elif this.belong == 1:
                e1.capacity += 1
            elif this.belong == 2:
                e2.capacity += 1
            elif this.belong == 3:
                e3.capacity += 1
    blist = new



class ExitGame(Exception):
        def __init__(self, value):
            self.value = value

        def __str__(self):
            return repr(self.value)

def Redraw():
    Shaders.quickdraw.DrawBlocks(Map)

first_run = True
def bedwar():
    global screen, Map, first_run, blist, TNTList, icelist
    mods.Iris.on_NewGame()
    blist = []
    TNTList = []
    icelist = []
    if not mods.cfg.SimplifyOutputInformation:
        printm('修改重生点信息。')
    Map[3][3].mtype = Bed
    Map[11][16].mtype = Bed
    for x in range(15, 18):
        for y in range(2, 5):
            Map[y][x].mtype = Void
    for x in range(2, 5):
        for y in range(10, 13):
            Map[y][x].mtype = Void
    for row in range(15):
        for col in range(20):
            draw_block(col, row, Map[row][col].mtype)
    if not mods.cfg.SimplifyOutputInformation:
        printm('显示地图完毕。')
    e1, e2, e3, pl = Enemy(1), Enemy(2), Enemy(3), Player()
    if not mods.cfg.SimplifyOutputInformation:
        printm('实体信息加载完毕。')
    #enemy_lived = 3
    power_max = 2 if mods.cfg.PowerIncrease == True else 3
    power_time = ftps() + 120_000
    Score_time = ftps() + 20_000
    Capac_time = ftps() + 65_000
    Shield_time= ftps() + 36_000
    
    Title_Bar = [None, 0] # 显示在屏幕中心的文字。用于提示事件。格式: [what:Union[None, str], tps_add:int]
    Player_bed_has_been_destroyed = False
    
    #cpu_per = psutil.cpu_percent()
    #mem_per = psutil.virtual_memory().percent
    cpu_mem_TPS = ftps() + 1000
    
    MovBombTPS = ftps()
    MovTNT_TPS = ftps()
    MovIceThornTps = ftps() + 50
    FPS_Tick = ftps() + 10
    #first_run = True
    fps_get_now = 0
    Time_Start = ftps()
    while True:
        fps.tick(mods.cfg.FPS_limit)
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                raise ExitGame('Click quit button.')
        
        if Map[11][16].mtype != Bed and Player_bed_has_been_destroyed == False:
            Title_Bar[0] = '重生点已被摧毁'
            Title_Bar[1] = ftps() + 3_800
            Player_bed_has_been_destroyed = True
        if Map[3][3].mtype != Bed and e1.health<=0 and e2.health<=0 and e3.health <=0 and not e1.shield and not e2.shield and not e3.shield:
            printm('Player win.')
            Time_Used = ftps() - Time_Start
            textWin = f.render(f'You Win! Use time: {Time_Used/1000:5.1f}', True, (255, 80, 80))
            screen.blit(textWin, (1220, 600))
            mods.printlib.xyprint('       ~ ~ ~ You Win! ~ ~ ~', color = (0, 255, 100), place = ('center', 8))
            pygame.display.flip()
            pygame.time.wait(3_000)
            raise ExitGame('Game finish.')
        if Map[11][16].mtype != Bed and pl.health <= 0 and not pl.shield:
            printm('Player lose.')
            Time_Used = ftps() - Time_Start
            textLose = f.render(f'You lose. Use time: {Time_Used/1000:5.1f}', True, (80, 80, 255))
            screen.blit(textLose, (1220, 600))
            mods.printlib.xyprint('       - _ - You lose. - _ -', color = (0, 255, 100), place = ('center', 8))
            pygame.display.flip()
            pygame.time.wait(3_000)
            raise ExitGame('Game over.')
        if ftps() >= power_time:
            power_time = ftps() + 60_000
            if mods.cfg.PowerIncrease == True:
                power_max += 1
                if not mods.cfg.SimplifyOutputInformation:
                    printm('增加炸弹威力。')
                Title_Bar[0] = '增加炸弹威力'
                Title_Bar[1] = ftps() + 3_800
        if ftps() >= Score_time:
            Score_time = ftps() + 15_000
            if mods.cfg.DoGoalIncrease == True:
                pl.goal += 1
                e1.goal += 1
                e2.goal += 1
                e3.goal += 1
                if not mods.cfg.SimplifyOutputInformation:
                    printm('增加分数')
                Title_Bar[0] = '增加分数'
                Title_Bar[1] = ftps() + 3_800
        if ftps() >= Capac_time:
            Capac_time = ftps() + 37_000
            if mods.cfg.DoCapacityIncrease == True:
                pl.capacity += 1
                e1.capacity += 1
                e2.capacity += 1
                e3.capacity += 1
                if not mods.cfg.SimplifyOutputInformation:
                    printm('增加炸弹容量。')
                Title_Bar[0] = '增加炸弹容量'
                Title_Bar[1] = ftps() + 3_800
        if ftps() >= Shield_time:
            Shield_time = ftps() + 38_000
            if mods.cfg.DoEquipmentShield == True:
                if pl.health > 0:
                    pl.shield = True
                if e1.health > 0:
                    e1.shield = True
                if e2.health > 0:
                    e2.shield = True
                if e3.health > 0:
                    e3.shield = True
                if not mods.cfg.SimplifyOutputInformation:
                    printm('自动装备护盾')
                Title_Bar[0] = '自动装备护盾'
                Title_Bar[1] = ftps() + 3_800
        
        
        fps_get_now = fps.get_fps()
        if ftps() >= FPS_Tick:
            pygame.draw.rect(screen, (0,0,0), (1201, 0, 298, 900))
            if mods.cfg.SimplifyDebuggingInformation == True:
                Text1 = f.render(f'FPS: {fps_get_now:3.2f} limit:{"垂直同步" if mods.cfg.FPS_limit == 60 else mods.cfg.FPS_limit if mods.cfg.FPS_limit <= 1000 else "无限制"} power: {power_max}', True, (255, 0 ,0))
                screen.blit(Text1, (1220, 20))
                Text_player = f.render(f'({pl.goal})Player: ({pl.x},{pl.y}) {"live" if pl.health>0 else "died"} kill: {pl.kill} died: {pl.died}', True, (0,255,0))
                screen.blit(Text_player, (1201, 50))
                Text_e1 = f.render(f'({e1.goal})Enemy1: ({e1.x},{e1.y}) {"live" if e1.health>0 else "died"} kill: {e1.kill} died: {e1.died}', True, (0, 255, 0))
                screen.blit(Text_e1, (1201, 80))
                Text_e2 = f.render(f'({e2.goal})Enemy2: ({e2.x},{e2.y}) {"live" if e2.health>0 else "died"} kill: {e2.kill} died: {e2.died}', True, (0, 255, 0))
                screen.blit(Text_e2, (1201, 110))
                Text_e3 = f.render(f'({e3.goal})Enemy3: ({e3.x},{e3.y}) {"live" if e3.health>0 else "died"} kill: {e3.kill} died: {e3.died}', True, (0, 255, 0))
                screen.blit(Text_e3, (1201, 140))
            else:
                mods.printlib.OutTextxy(f'FPS: {fps_get_now:3.2f} limit:{"垂直同步" if mods.cfg.EnableVerticalSynchronization == True else mods.cfg.FPS_limit if mods.cfg.FPS_limit <= 1000 else "无限制"} power: {power_max}', (255,0,0), ('right', 0))
                mods.printlib.OutTextxy(f'Player:  Position: ({pl.x},{pl.y})  {"live" if pl.health>0 else "died"}', (0, 255, 255), ('right', 1))
                mods.printlib.OutTextxy(f'    Goal:{pl.goal} Kill:{pl.kill} Death:{pl.died}', (0,255,255), ('right', 2))
                mods.printlib.OutTextxy(f'    Capacity:{pl.capacity} Rebirth:{cint(pl.rebirth/100)}', (0,255,255), ('right', 3))
                mods.printlib.OutTextxy(f'Enemy1:  Position: ({e1.x},{e1.y})  {"live" if e1.health>0 else "died"}', mods.cfg.color_e1, ('right', 4))
                mods.printlib.OutTextxy(f'    Goal:{e1.goal} Kill:{e1.kill} Death:{e1.died}', mods.cfg.color_e1, ('right', 5))
                mods.printlib.OutTextxy(f'    Capacity:{e1.capacity} Rebirth:{cint(e1.rebirth/20)}', mods.cfg.color_e1, ('right', 6))
                mods.printlib.OutTextxy(f'Enemy2:  Position: ({e2.x},{e2.y})  {"live" if e2.health>0 else "died"}', mods.cfg.color_e2, ('right', 7))
                mods.printlib.OutTextxy(f'    Goal:{e2.goal} Kill:{e2.kill} Death:{e2.died}', mods.cfg.color_e2, ('right', 8))
                mods.printlib.OutTextxy(f'    Capacity:{e2.capacity} Rebirth:{cint(e2.rebirth/20)}', mods.cfg.color_e2, ('right', 9))
                mods.printlib.OutTextxy(f'Enemy3:  Position: ({e3.x},{e3.y})  {"live" if e3.health>0 else "died"}', mods.cfg.color_e3, ('right', 10))
                mods.printlib.OutTextxy(f'    Goal:{e3.goal} Kill:{e3.kill} Death:{e3.died}', mods.cfg.color_e3, ('right', 11))
                mods.printlib.OutTextxy(f'    Capacity:{e3.capacity} Rebirth:{cint(e3.rebirth/20)}', mods.cfg.color_e3, ('right', 12))
                
                # 状态指示器
                if pl.shield:
                    mods.Iris.draw_image('effect_shield', (1469, 2 * 30 + 10))
                if pl.slow >= 0:
                    mods.Iris.draw_image('effect_slow', (1469, 3 * 30 + 10))
                if e1.shield:
                    mods.Iris.draw_image('effect_shield', (1469, 5 * 30 + 10))
                if e1.slow >= 0:
                    mods.Iris.draw_image('effect_slow', (1469, 6 * 30 + 10))
                if e2.shield:
                    mods.Iris.draw_image('effect_shield', (1469, 8 * 30 + 10))
                if e2.slow >= 0:
                    mods.Iris.draw_image('effect_slow', (1469, 9 * 30 + 10))
                if e3.shield:
                    mods.Iris.draw_image('effect_shield', (1469, 11 * 30 + 10))
                if e3.slow >= 0:
                    mods.Iris.draw_image('effect_slow', (1469, 12 * 30 + 10))
                
                
                if not mods.cfg.PowerIncrease and not mods.cfg.DoGoalIncrease and not mods.cfg.DoCapacityIncrease and not mods.cfg.DoEquipmentShield:
                    Ellipsis
                else:
                    pot, dgt, dct, sht = power_time, Score_time, Capac_time, Shield_time
                    if not mods.cfg.PowerIncrease:
                        pot = 922_114_514_1919810_0
                    if not mods.cfg.DoGoalIncrease:
                        dgt = 922_114_514_1919810_0
                    if not mods.cfg.DoCapacityIncrease:
                        dct = 922_114_514_1919810_0
                    if not mods.cfg.DoEquipmentShield:
                        sht = 922_114_514_1919810_0
                    time_min = min(pot, dgt, dct, sht)
                    if pot == time_min:
                        mods.printlib.OutTextxy(f'下一事件: 增加炸弹威力    倒计时: {cint((power_time - ftps()) / 1000)} s', (255, 255, 0), ('right', 14))
                    elif dgt == time_min:
                        mods.printlib.OutTextxy(f'下一事件: 增加分数    倒计时: {cint((Score_time - ftps()) / 1000)} s', (255, 255, 0), ('right', 14))
                    elif sht == time_min:
                        mods.printlib.OutTextxy(f'下一事件: 装备护盾    倒计时: {cint((Shield_time - ftps()) / 1000)} s', (255, 255, 0), ('right', 14))
                    else:
                        mods.printlib.OutTextxy(f'下一事件: 增加炸弹容量    倒计时: {cint((Capac_time - ftps()) / 1000)} s', (255, 255, 0), ('right', 14))
                mods.printlib.xyprint(f'Facing: {pl.facing}', (255, 255, 255), ('right', 12))
                
                #mods.printlib.xyprint(f'CPU: {cpu_per}  Memory: {mem_per}', (0, 255, 0), ('right', 17))
            
            FPS_Tick = ftps() + 100
        
        if first_run == True and not mods.cfg.SimplifyOutputInformation:
            printm('01 judge finished.')
        
        kp = pygame.key.get_pressed()
        if first_run == True and not mods.cfg.SimplifyOutputInformation:
            printm('获取按键信息。')
        if kp[pygame.K_p]:
            mods.CrystalLib.Pause()
            Redraw()
        
        if ftps() > MovTNT_TPS:
            MovTNT(pl, e1, e2, e3)
            MovTNT_TPS = ftps() + 50
        
        if ftps()>= pl.nextTps:
            if pl.health <= 0:
                if pl.rebirth < 0:
                    pl.rebirth = 480 if pl.shield == False else 1
                    pl.died += 1
                    #pl.goal = 0
                elif pl.rebirth == 0:
                    pl.x = 16
                    pl.y = 11
                    pl.health = 1
                    pl.rebirth = -1
                    pl.xp = 0
                    pl.yp = 0
                    pl.using_b = False
                    if not pl.shield:
                        pl.goal = 0
                        pl.frozen = False
                        pl.slow = -1
                        pl.speed = mods.cfg.player_speed()
                    else:
                        pl.shield = False
                else:
                    pl.rebirth -= 1
            
            if pl.xp > 0:
                pl.xp += pl.speed
                if pl.xp > 60:
                    pl.xp = 0
                    pl.x += 1
            elif pl.yp > 0:
                pl.yp += pl.speed
                if pl.yp > 60:
                    pl.yp = 0
                    pl.y += 1
            elif pl.xp < 0:
                pl.xp -= pl.speed
                if pl.xp < -60:
                    pl.xp = 0
                    pl.x -= 1
            elif pl.yp < 0:
                pl.yp -= pl.speed
                if pl.yp < -60:
                    pl.yp = 0
                    pl.y -= 1
            else:
                if kp[pygame.K_UP] and cango(pl, 'north') and pl.health > 0 and not pl.frozen:
                    pl.yp = -1
                elif kp[pygame.K_DOWN] and cango(pl, 'south') and pl.health > 0 and not pl.frozen:
                    pl.yp = 1
                elif kp[pygame.K_LEFT] and cango(pl, 'west') and pl.health > 0 and not pl.frozen:
                    pl.xp = -1
                elif kp[pygame.K_RIGHT] and cango(pl, 'east') and pl.health > 0 and not pl.frozen:
                    pl.xp = 1
            if pl.health > 0 and not pl.frozen:
                if kp[pygame.K_UP]:
                    pl.facing = 'north'
                elif kp[pygame.K_DOWN]:
                    pl.facing = 'south'
                elif kp[pygame.K_LEFT]:
                    pl.facing = 'west'
                elif kp[pygame.K_RIGHT]:
                    pl.facing = 'east'
            
            if pl.ender_pearl_timeout > 0:
                pl.ender_pearl_timeout -= 1
            if kp[pygame.K_w] and pl.ender_pearl_timeout <= 0 and pl.goal>=2 and pl.health > 0:
                pl.ender_pearl_timeout = 200
                pl.y -= mods.cfg.item_ender_pearl_distance()
                if pl.y < 0:
                    pl.y = 0
                pl.goal -= 2
            if kp[pygame.K_a] and pl.ender_pearl_timeout <= 0 and pl.goal>=2 and pl.health > 0:
                pl.ender_pearl_timeout = 200
                pl.x -= mods.cfg.item_ender_pearl_distance()
                if pl.x < 0:
                    pl.x = 0
                pl.goal -= 2
            if kp[pygame.K_s] and pl.ender_pearl_timeout <= 0 and pl.goal>=2 and pl.health > 0:
                pl.ender_pearl_timeout = 200
                pl.y += mods.cfg.item_ender_pearl_distance()
                if pl.y > 14:
                    pl.y = 14
                pl.goal -= 2
            if kp[pygame.K_d] and pl.ender_pearl_timeout <= 0 and pl.goal>=2 and pl.health > 0:
                pl.ender_pearl_timeout = 200
                pl.x += mods.cfg.item_ender_pearl_distance()
                if pl.x > 19:
                    pl.x = 19
                pl.goal -= 2
            
            if pl.tnt_timeout > 0:
                pl.tnt_timeout -= 1
            if kp[pygame.K_t] and pl.tnt_timeout <= 0 and pl.goal >= 3 and pl.health > 0 and not pl.frozen:
                pl.tnt_timeout = 60
                if pl.facing == 'west':
                    Create_TNT(TNT(
                        y = pl.y, x = max(0, pl.x - mods.cfg.TNTPower), belong = 0,Time=ftps()+1_600,
                        xFrom=pl.x, yFrom=pl.y
                    ))
                    pl.goal -= 3
                elif pl.facing == 'east':
                    Create_TNT(TNT(
                        y = pl.y, x = min(19, pl.x + mods.cfg.TNTPower), belong = 0,Time=ftps()+1_600,
                        xFrom=pl.x, yFrom=pl.y
                    ))
                    pl.goal -= 3
                elif pl.facing == 'south':
                    Create_TNT(TNT(
                        x = pl.x, y = min(14, pl.y + mods.cfg.TNTPower), belong = 0,Time=ftps()+1_600,
                        xFrom=pl.x, yFrom=pl.y
                    ))
                    pl.goal -= 3
                else:
                    Create_TNT(TNT(
                        x = pl.x, y = max(0, pl.y - mods.cfg.TNTPower), belong = 0,Time=ftps()+1_600,
                        xFrom=pl.x, yFrom=pl.y
                    ))
                    pl.goal -= 3
            
            if pl.ice_timeout > 0:
                pl.ice_timeout -= 1
            if kp[pygame.K_i] and pl.ice_timeout <= 0 and pl.goal >= 1 and pl.health > 0 and not pl.frozen:
                pl.ice_timeout = 70
                xn, yn = pl.x, pl.y
                if pl.facing == 'east':
                    xn += 1
                elif pl.facing == 'south':
                    yn += 1
                elif pl.facing == 'west':
                    xn -= 1
                else:
                    yn -= 1
                if 0 <= xn <= 19 and 0 <= yn <= 14:
                    icelist.append(IceThorn(x = xn, y = yn, direct = pl.facing, Time = 0))
            
            if kp[pygame.K_SPACE] and pl.capacity > 0 and not hasb(pl.x, pl.y) and pl.health > 0 and Map[pl.y][pl.x].mtype == Void and not pl.frozen:
                CreateBomb(Bomb(x = pl.x, y = pl.y, Time = ftps() + 2_200, belong = 0, color = (50, 255, 255), power = power_max))
                pl.capacity -= 1
            if kp[pygame.K_b] and Map[pl.y][pl.x].mtype == Void and pl.health > 0 and not pl.frozen:
                Map[pl.y][pl.x].mtype = Soft
            
            pl.nextTps = ftps() + 10
        if first_run and not mods.cfg.SimplifyOutputInformation:
            printm('获取Player操作信息。')
        
        #
        
        # 渲染顺序：
        # entity_around
        # MovBomb (Bomb_around + Bomb + bbs)
        # entity
        
        #
        
        # New (slower, but no bugs?)
        for x in range(0, 20):
            for y in range(0, 15):
                draw_block(x, y, Map[y][x].mtype)
        
        # In new render plan, there's no need forus to render entities's env anymore!
        #MovBomb(pl, e1, e2, e3, isOnlyRender = True)
        if ftps() >= MovIceThornTps:
            movIceThorn(tuple([pl, e1, e2, e3]), False)
            MovIceThornTps = ftps() + 50
        else:
            movIceThorn(tuple([pl, e1, e2, e3]), isOnlyRender = True)
        pl.mov_effect()
        if pl.ender_pearl_timeout != 0:
            mods.Iris.draw_particle_enderpearl(pl.x * 60 + pl.xp, pl.y * 60 + pl.yp)
        #if TNTList != []: //20240103 改变渲染顺序
        #    for t in TNTList:
        #        mods.Iris.draw_item(t)
        
        if first_run == True and not mods.cfg.SimplifyOutputInformation:
            printm('地图更新完成。')
        
        if Title_Bar[0] != None:
            mods.printlib.TitleBarPrint(Title_Bar[0])
            for x in range(5, 15):
                for y in range(6, 12):
                    draw_block(x, y, Map[y][x].mtype) # 这个东西会导致炸弹显示不出来!!!!!!!!!!!!!!!!!!
            if ftps() > Title_Bar[1]:
                Title_Bar[0] = None
                Redraw()
        
        MovBombNeo(pl, e1, e2, e3)
        if TNTList != []: # //20240103 改变渲染顺序
            for t in TNTList:
                mods.Iris.draw_item(t)
        if ftps() >= e1.nextTps:
            e1.run(pl, power_max, e2, e3)
        if e1.health > 0:
            mods.Iris.draw_entity(e1, screen)
            if e1.ender_pearl_timeout != 0:
                mods.Iris.draw_particle_enderpearl(e1.x * 60 + e1.xp, e1.y * 60 + e1.yp)
        if ftps() >= e2.nextTps:
            e2.run(pl, power_max, e1, e3)
        if e2.health > 0:
            mods.Iris.draw_entity(e2, screen)
            if e1.ender_pearl_timeout != 0:
                mods.Iris.draw_particle_enderpearl(e1.x * 60 + e1.xp, e1.y * 60 + e1.yp)
        if ftps() >= e3.nextTps:
            e3.run(pl, power_max, e1, e2)
        if e3.health > 0:
            mods.Iris.draw_entity(e3, screen)
            if e1.ender_pearl_timeout != 0:
                mods.Iris.draw_particle_enderpearl(e1.x * 60 + e1.xp, e1.y * 60 + e1.yp)
        if first_run and not mods.cfg.SimplifyOutputInformation:
            printm('获取NPC信息。')
        if pl.health > 0:
            mods.Iris.draw_entity(pl, screen)
        if first_run == True:
            if not mods.cfg.SimplifyOutputInformation:
                printm('绘制完成。')
            printm('循环检查完成。')
            first_run = False
        #pygame.display.update()
        pygame.display.flip()
        
        
# 20240103
def Normal():
    global screen, Map, first_run, blist, TNTList, icelist, RocketList, GB_LEVEL_SHUT, MineList
    mods.Iris.on_NewGame()
    blist = []
    TNTList = []
    icelist = []
    RocketList = []
    MineList = []
    if not mods.cfg.SimplifyOutputInformation:
        printm('修改重生点信息。')
    Map[3][3].mtype = Void
    Map[11][16].mtype = Void
    for x in range(15, 18):
        for y in range(2, 5):
            Map[y][x].mtype = Void
    for x in range(2, 5):
        for y in range(10, 13):
            Map[y][x].mtype = Void
    for row in range(15):
        for col in range(20):
            draw_block(col, row, Map[row][col].mtype)
    if not mods.cfg.SimplifyOutputInformation:
        printm('显示地图完毕。')
    e1, e2, e3, pl = Enemy(1), Enemy(2), Enemy(3), Player(0)
    pl.capacity = 1
    e1.capacity = 1
    e2.capacity = 1
    e3.capacity = 1
    if not mods.cfg.SimplifyOutputInformation:
        printm('实体信息加载完毕。')
    #enemy_lived = 3
    #power_max = 2 if mods.cfg.PowerIncrease == True else 3
    power_max : int = 2
    
    Title_Bar = [None, 0] # 显示在屏幕中心的文字。用于提示事件。格式: [what:Union[None, str], tps_add:int]
    Player_bed_has_been_destroyed = True
    
    MovTNT_TPS = ftps()
    MovIceThornTps = ftps() + 50
    FPS_Tick = ftps() + 10
    MovRocketTPS = ftps() + 20
    New_Rocket_List = []
    #first_run = True
    fps_get_now = 0
    Time_Start = ftps()
    MAIN_TPS : int = ftps() # 1.12.2
    ShowFPSTime = MAIN_TPS
    
    # *** *** Normal & Level Only
    TimeScoreTPS : int = MAIN_TPS
    if GB_NOW == GB_LEVEL:
        ST_LEVELGOALS.timescore = [0,0,0,0]
        ST_LEVELGOALS.scores = [0,0,0,0]
    
    # 1.16.5
    Monitor.start('Normal')
    while True:
        fps.tick(mods.cfg.FPS_limit)
        MAIN_TPS = ftps()
        
        # 1.16.5
        for es in (pl, e1, e2, e3):
            if 0 <= es.x < 20 and 0 <= es.y < 15:
                pass
            else:
                Monitor.close()
                raise Exception(f'Place_out_of_range: {es.ID}: ({es.x}, {es.y})')
        
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                GB_LEVEL_SHUT = True
                Monitor.close()
                raise ExitGame('Click quit button.')
        
        if Map[11][16].mtype != Bed and Player_bed_has_been_destroyed == False:
            Title_Bar[0] = '重生点已被摧毁'
            Title_Bar[1] = MAIN_TPS + 3_800
            Player_bed_has_been_destroyed = True
        if Map[3][3].mtype != Bed and e1.health<=0 and e2.health<=0 and e3.health <=0 and not e1.shield and not e2.shield and not e3.shield:
            printm('Player win.')
            if GB_NOW == GB_LEVEL:
                for i in range(4):
                    ST_LEVELGOALS.totalscore[i] += ST_LEVELGOALS.timescore[i]
                    ST_LEVELGOALS.totalscore[i] += ST_LEVELGOALS.scores[i]
            Time_Used = MAIN_TPS - Time_Start
            textWin = f.render(f'You Win! Use time: {Time_Used/1000:5.1f}', True, (255, 80, 80))
            screen.blit(textWin, (1220, 600))
            mods.printlib.xyprint('       ~ ~ ~ You Win! ~ ~ ~', color = (0, 255, 100), place = ('center', 8))
            pygame.display.flip()
            Monitor.close()
            pygame.time.wait(3_000)
            raise ExitGame('Game finish.')
        if Map[11][16].mtype != Bed and pl.health <= 0 and not pl.shield:
            printm('Player lose.')
            if GB_NOW == GB_LEVEL:
                ST_LEVELGOALS.scores[3] += 3000
                ST_LEVELGOALS.scores[2] += 3000
                ST_LEVELGOALS.scores[1] += 3000
                for i in range(4):
                    ST_LEVELGOALS.totalscore[i] += ST_LEVELGOALS.timescore[i]
                    ST_LEVELGOALS.totalscore[i] += ST_LEVELGOALS.scores[i]
            Time_Used = MAIN_TPS - Time_Start
            textLose = f.render(f'You lose. Use time: {Time_Used/1000:5.1f}', True, (80, 80, 255))
            screen.blit(textLose, (1220, 600))
            mods.printlib.xyprint('       - _ - You lose. - _ -', color = (0, 255, 100), place = ('center', 8))
            pygame.display.flip()
            Monitor.close()
            pygame.time.wait(3_000)
            raise ExitGame('Game over.')
        
        
        
        #fps_get_now = fps.get_fps()
        if MAIN_TPS >= FPS_Tick:
            fps_get_now = fps.get_fps()
            pygame.draw.rect(screen, (0,0,0), (1201, 0, 298, 900))
            if mods.cfg.SimplifyDebuggingInformation == True:
                Text1 = f.render(f'FPS: {fps_get_now:3.2f} limit:{"垂直同步" if mods.cfg.FPS_limit == 60 else mods.cfg.FPS_limit if mods.cfg.FPS_limit <= 1000 else "无限制"} power: {power_max}', True, (255, 0 ,0))
                screen.blit(Text1, (1220, 20))
                Text_player = f.render(f'({pl.goal})Player: ({pl.x},{pl.y}) {"live" if pl.health>0 else "died"} kill: {pl.kill} died: {pl.died}', True, (0,255,0))
                screen.blit(Text_player, (1201, 50))
                Text_e1 = f.render(f'({e1.goal})Enemy1: ({e1.x},{e1.y}) {"live" if e1.health>0 else "died"} kill: {e1.kill} died: {e1.died}', True, (0, 255, 0))
                screen.blit(Text_e1, (1201, 80))
                Text_e2 = f.render(f'({e2.goal})Enemy2: ({e2.x},{e2.y}) {"live" if e2.health>0 else "died"} kill: {e2.kill} died: {e2.died}', True, (0, 255, 0))
                screen.blit(Text_e2, (1201, 110))
                Text_e3 = f.render(f'({e3.goal})Enemy3: ({e3.x},{e3.y}) {"live" if e3.health>0 else "died"} kill: {e3.kill} died: {e3.died}', True, (0, 255, 0))
                screen.blit(Text_e3, (1201, 140))
            else:
                mods.printlib.OutTextxy(f'FPS: {fps_get_now:3.2f} limit:{"垂直同步" if mods.cfg.EnableVerticalSynchronization == True else mods.cfg.FPS_limit if mods.cfg.FPS_limit <= 1000 else "无限制"} power: {power_max}', (255,0,0), ('right', 0))
                mods.printlib.OutTextxy(f'Player:  Position: ({pl.x},{pl.y})  {"live" if pl.health>0 else "died"}', (0, 255, 255), ('right', 1))
                mods.printlib.OutTextxy(f'    Goal:{pl.goal} Kill:{pl.kill} Death:{pl.died} Mine:{pl.hold_mine}', (0,255,255), ('right', 2))
                mods.printlib.OutTextxy(f'    Capacity:{pl.capacity} TNT:{pl.hold_tnt} Power:{pl.power} Rocket:{pl.rocket}', (0,255,255), ('right', 3))
                mods.printlib.OutTextxy(f'Enemy1:  Position: ({e1.x},{e1.y})  {"live" if e1.health>0 else "died"}', mods.cfg.color_e1, ('right', 4))
                mods.printlib.OutTextxy(f'    Goal:{e1.goal} Kill:{e1.kill} Death:{e1.died} Mine:{e1.hold_mine}', mods.cfg.color_e1, ('right', 5))
                mods.printlib.OutTextxy(f'    Capacity:{e1.capacity} TNT:{e1.hold_tnt} Power:{e1.power} Rocket:{e1.rocket}', mods.cfg.color_e1, ('right', 6))
                mods.printlib.OutTextxy(f'Enemy2:  Position: ({e2.x},{e2.y})  {"live" if e2.health>0 else "died"}', mods.cfg.color_e2, ('right', 7))
                mods.printlib.OutTextxy(f'    Goal:{e2.goal} Kill:{e2.kill} Death:{e2.died} Mine:{e2.hold_mine}', mods.cfg.color_e2, ('right', 8))
                mods.printlib.OutTextxy(f'    Capacity:{e2.capacity} TNT:{e2.hold_tnt} Power:{e2.power} Rocket:{e2.rocket}', mods.cfg.color_e2, ('right', 9))
                mods.printlib.OutTextxy(f'Enemy3:  Position: ({e3.x},{e3.y})  {"live" if e3.health>0 else "died"}', mods.cfg.color_e3, ('right', 10))
                mods.printlib.OutTextxy(f'    Goal:{e3.goal} Kill:{e3.kill} Death:{e3.died} Mine:{e3.hold_mine}', mods.cfg.color_e3, ('right', 11))
                mods.printlib.OutTextxy(f'    Capacity:{e3.capacity} TNT:{e3.hold_tnt} Power:{e3.power} Rocket:{e3.rocket}', mods.cfg.color_e3, ('right', 12))
                
                # 状态指示器
                if pl.shield:
                    mods.Iris.draw_image('effect_shield', (1469, 2 * 30 + 10))
                if pl.slow >= 0:
                    mods.Iris.draw_image('effect_slow', (1469, 3 * 30 + 10))
                if e1.shield:
                    mods.Iris.draw_image('effect_shield', (1469, 5 * 30 + 10))
                if e1.slow >= 0:
                    mods.Iris.draw_image('effect_slow', (1469, 6 * 30 + 10))
                if e2.shield:
                    mods.Iris.draw_image('effect_shield', (1469, 8 * 30 + 10))
                if e2.slow >= 0:
                    mods.Iris.draw_image('effect_slow', (1469, 9 * 30 + 10))
                if e3.shield:
                    mods.Iris.draw_image('effect_shield', (1469, 11 * 30 + 10))
                if e3.slow >= 0:
                    mods.Iris.draw_image('effect_slow', (1469, 12 * 30 + 10))
                
                # Scores in level mode
                if GB_NOW == GB_LEVEL:
                    mods.printlib.OutTextxy(
                        f"E0: {ST_LEVELGOALS.scores[0]} E1: {ST_LEVELGOALS.scores[1]} E2: {ST_LEVELGOALS.scores[2]} E3: {ST_LEVELGOALS.scores[3]}",
                        (8, 255, 169), ('right', 14)
                    )
                    #mods.printlib.OutTextxy(
                    #    f"Time score: <{ST_LEVELGOALS.timescore[0]}, {ST_LEVELGOALS.timescore[1]}, {ST_LEVELGOALS.timescore[2]}, {ST_LEVELGOALS.timescore[3]}>",
                    #    (8, 255, 169), ('right', 15)
                    #)
                    mods.printlib.OutTextxy(
                        f"Lvl: {GB_LEVEL_LVL} / 25", (8, 120, 255), ('right', 15)
                    )
                mods.printlib.xyprint(f'Facing: {pl.facing}', (255, 255, 255), ('right', 12))
                
                #mods.printlib.xyprint(f'CPU: {cpu_per}  Memory: {mem_per}', (0, 255, 0), ('right', 17))
            
            FPS_Tick = MAIN_TPS + 100
        
        if first_run == True and not mods.cfg.SimplifyOutputInformation:
            printm('01 judge finished.')
        
        kp = pygame.key.get_pressed()
        if first_run == True and not mods.cfg.SimplifyOutputInformation:
            printm('获取按键信息。')
        if kp[pygame.K_p]:
            mods.CrystalLib.Pause()
            Redraw()
        
        if MAIN_TPS > MovTNT_TPS:
            MovTNT(pl, e1, e2, e3)
            MovTNT_TPS = MAIN_TPS + 50
        if MAIN_TPS > MovRocketTPS:
            MovRocketTPS = MAIN_TPS + 20
            if len(RocketList) != 0:
                New_Rocket_List = []
                for r in RocketList:
                    MovRocket(r, pl, e1, e2, e3)
                    if r.live == True:
                        New_Rocket_List.append(r)
                    else:
                        pass
                RocketList = New_Rocket_List
        # 1.12.2
        if MAIN_TPS > TimeScoreTPS:
            TimeScoreTPS = MAIN_TPS + 169
            if GB_NOW == GB_LEVEL:
                for e in ((pl, 0),  (e1, 1),  (e2, 2),  (e3, 3)):
                    if e[0].health > 0:
                        ST_LEVELGOALS.timescore[e[1]] += 1
        if MAIN_TPS>= pl.nextTps:
            if pl.health <= 0:
                if pl.rebirth < 0:
                    pl.rebirth = 480 if pl.shield == False else 1
                    pl.died += 1
                    #pl.goal = 0
                elif pl.rebirth == 0:
                    pl.x = 16
                    pl.y = 11
                    pl.health = 1
                    pl.rebirth = -1
                    pl.xp = 0
                    pl.yp = 0
                    pl.using_b = False
                    if not pl.shield:
                        pl.goal = 0
                        pl.frozen = False
                        pl.slow = -1
                        pl.speed = mods.cfg.player_speed()
                    else:
                        pl.shield = False
                else:
                    pl.rebirth -= 1
            
            if pl.xp > 0:
                pl.xp += pl.speed
                if pl.xp > 60:
                    pl.xp = 0
                    pl.x += 1
            elif pl.yp > 0:
                pl.yp += pl.speed
                if pl.yp > 60:
                    pl.yp = 0
                    pl.y += 1
            elif pl.xp < 0:
                pl.xp -= pl.speed
                if pl.xp < -60:
                    pl.xp = 0
                    pl.x -= 1
            elif pl.yp < 0:
                pl.yp -= pl.speed
                if pl.yp < -60:
                    pl.yp = 0
                    pl.y -= 1
            else:
                if kp[pygame.K_UP] and cango(pl, 'north') and pl.health > 0 and not pl.frozen:
                    pl.yp = -1
                elif kp[pygame.K_DOWN] and cango(pl, 'south') and pl.health > 0 and not pl.frozen:
                    pl.yp = 1
                elif kp[pygame.K_LEFT] and cango(pl, 'west') and pl.health > 0 and not pl.frozen:
                    pl.xp = -1
                elif kp[pygame.K_RIGHT] and cango(pl, 'east') and pl.health > 0 and not pl.frozen:
                    pl.xp = 1
            if pl.health > 0 and not pl.frozen:
                if kp[pygame.K_UP]:
                    pl.facing = 'north'
                elif kp[pygame.K_DOWN]:
                    pl.facing = 'south'
                elif kp[pygame.K_LEFT]:
                    pl.facing = 'west'
                elif kp[pygame.K_RIGHT]:
                    pl.facing = 'east'
            
            if pl.ender_pearl_timeout > 0:
                pl.ender_pearl_timeout -= 1
            if kp[pygame.K_w] and pl.ender_pearl_timeout <= 0 and pl.goal>=2 and pl.health > 0:
                pl.ender_pearl_timeout = 200
                pl.y -= mods.cfg.item_ender_pearl_distance()
                if pl.y < 0:
                    pl.y = 0
                pl.goal -= 2
            if kp[pygame.K_a] and pl.ender_pearl_timeout <= 0 and pl.goal>=2 and pl.health > 0:
                pl.ender_pearl_timeout = 200
                pl.x -= mods.cfg.item_ender_pearl_distance()
                if pl.x < 0:
                    pl.x = 0
                pl.goal -= 2
            if kp[pygame.K_s] and pl.ender_pearl_timeout <= 0 and pl.goal>=2 and pl.health > 0:
                pl.ender_pearl_timeout = 200
                pl.y += mods.cfg.item_ender_pearl_distance()
                if pl.y > 14:
                    pl.y = 14
                pl.goal -= 2
            if kp[pygame.K_d] and pl.ender_pearl_timeout <= 0 and pl.goal>=2 and pl.health > 0:
                pl.ender_pearl_timeout = 200
                pl.x += mods.cfg.item_ender_pearl_distance()
                if pl.x > 19:
                    pl.x = 19
                pl.goal -= 2
            
            if pl.tnt_timeout > 0:
                pl.tnt_timeout -= 1
            if pl.rocket_timeout > 0:
                pl.rocket_timeout -= 1
            if kp[pygame.K_t] and pl.tnt_timeout <= 0 and pl.hold_tnt >0 and pl.health > 0 and not pl.frozen:
                pl.tnt_timeout = 40
                if pl.facing == 'west':
                    Create_TNT(TNT(
                        y = pl.y, x = max(0, pl.x - mods.cfg.TNTPower), belong = 0,Time=MAIN_TPS+1_600,
                        xFrom=pl.x, yFrom=pl.y
                    ))
                    pl.hold_tnt -= 1
                elif pl.facing == 'east':
                    Create_TNT(TNT(
                        y = pl.y, x = min(19, pl.x + mods.cfg.TNTPower), belong = 0,Time=MAIN_TPS+1_600,
                        xFrom=pl.x, yFrom=pl.y
                    ))
                    pl.hold_tnt -= 1
                elif pl.facing == 'south':
                    Create_TNT(TNT(
                        x = pl.x, y = min(14, pl.y + mods.cfg.TNTPower), belong = 0,Time=MAIN_TPS+1_600,
                        xFrom=pl.x, yFrom=pl.y
                    ))
                    pl.hold_tnt -= 1
                else:
                    Create_TNT(TNT(
                        x = pl.x, y = max(0, pl.y - mods.cfg.TNTPower), belong = 0,Time=MAIN_TPS+1_600,
                        xFrom=pl.x, yFrom=pl.y
                    ))
                    pl.hold_tnt -= 1
            
            if kp[pygame.K_r] and pl.rocket_timeout <= 0 and pl.rocket > 0 and pl.health > 0 and not pl.frozen:
                pl.rocket_timeout = 32
                AddRocket(
                    pl.x, pl.y, pl.facing, pl.ID
                )
                pl.rocket -= 1
            if kp[pygame.K_m] and pl.hold_mine > 0 and pl.health > 0 and not pl.frozen and HaveMine(pl.x, pl.y)==False:
                Try_SetLandMine(pl)
            if pl.ice_timeout > 0:
                pl.ice_timeout -= 1
            if kp[pygame.K_i] and pl.ice_timeout <= 0 and pl.goal >= 1 and pl.health > 0 and not pl.frozen:
                pl.ice_timeout = 70
                xn, yn = pl.x, pl.y
                if pl.facing == 'east':
                    xn += 1
                elif pl.facing == 'south':
                    yn += 1
                elif pl.facing == 'west':
                    xn -= 1
                else:
                    yn -= 1
                if 0 <= xn <= 19 and 0 <= yn <= 14:
                    icelist.append(IceThorn(x = xn, y = yn, direct = pl.facing, Time = 0))
            
            if kp[pygame.K_SPACE] and pl.capacity > 0 and not hasb(pl.x, pl.y) and pl.health > 0 and Map[pl.y][pl.x].mtype == Void and not pl.frozen:
                CreateBomb(Bomb(x = pl.x, y = pl.y, Time = MAIN_TPS + 2_200, belong = 0, color = (50, 255, 255), power = pl.power))
                pl.capacity -= 1
            #if kp[pygame.K_b] and Map[pl.y][pl.x].mtype == Void and pl.health > 0 and not pl.frozen:
            #    Map[pl.y][pl.x].mtype = Soft
            
            pl.nextTps = MAIN_TPS + 10
            
            # 20240103
            #eat
            match Map[pl.y][pl.x].addon:
                case mods.Iris.MA_NULL:
                    pass
                case mods.Iris.MA_APPLE:
                    pl.capacity += 1
                    Map[pl.y][pl.x].addon = mods.Iris.MA_NULL
                    if GB_NOW == GB_LEVEL:
                        ST_LEVELGOALS.scores[pl.ID] += 20
                case mods.Iris.MA_POWER:
                    pl.power += 1
                    Map[pl.y][pl.x].addon = mods.Iris.MA_NULL
                    if GB_NOW == GB_LEVEL:
                        ST_LEVELGOALS.scores[pl.ID] += 20
                case mods.Iris.MA_TNT:
                    pl.hold_tnt += 1
                    Map[pl.y][pl.x].addon = mods.Iris.MA_NULL
                    if GB_NOW == GB_LEVEL:
                        ST_LEVELGOALS.scores[pl.ID] += 20
                case mods.Iris.MA_SHIELD:
                    pl.shield = True
                    Map[pl.y][pl.x].addon = mods.Iris.MA_NULL
                    if GB_NOW == GB_LEVEL:
                        ST_LEVELGOALS.scores[pl.ID] += 20
                    
                case mods.Iris.MA_ROCKET:
                    pl.rocket += 1
                    Map[pl.y][pl.x].addon = mods.Iris.MA_NULL
                    if GB_NOW == GB_LEVEL:
                        ST_LEVELGOALS.scores[pl.ID] += 20
                case mods.Iris.MA_DIAMOND:
                    Map[pl.y][pl.x].addon = mods.Iris.MA_NULL
                    if GB_NOW == GB_LEVEL:
                        ST_LEVELGOALS.scores[pl.ID] += 100
                case mods.Iris.MA_LANDMINE:
                    pl.hold_mine += 1
                    Map[pl.y][pl.x].addon = mods.Iris.MA_NULL
                    if GB_NOW == GB_LEVEL:
                        ST_LEVELGOALS.scores[pl.ID] += 20
                case _:
                    pass
        if first_run and not mods.cfg.SimplifyOutputInformation:
            printm('获取Player操作信息。')
        
        #
        
        # 渲染顺序：
        # entity_around
        # MovBomb (Bomb_around + Bomb + bbs)
        # entity
        
        #
        
        # New (slower, but no bugs?)
        #for x in range(0, 20):
        #    for y in range(0, 15):
        #        draw_block(x, y, Map[y][x].mtype)
        Shaders.quickdraw.DrawBlocks(Map)
        
        # In new render plan, there's no need forus to render entities's env anymore!
        #MovBomb(pl, e1, e2, e3, isOnlyRender = True)
        if MAIN_TPS >= MovIceThornTps:
            movIceThorn(tuple([pl, e1, e2, e3]), False)
            MovIceThornTps = MAIN_TPS + 50
        else:
            movIceThorn(tuple([pl, e1, e2, e3]), isOnlyRender = True)
        pl.mov_effect()
        if pl.ender_pearl_timeout != 0:
            mods.Iris.draw_particle_enderpearl(pl.x * 60 + pl.xp, pl.y * 60 + pl.yp)
        #if TNTList != []: //20240103 改变渲染顺序
        #    for t in TNTList:
        #        mods.Iris.draw_item(t)
        
        if first_run == True and not mods.cfg.SimplifyOutputInformation:
            printm('地图更新完成。')
        
        if Title_Bar[0] != None:
            mods.printlib.TitleBarPrint(Title_Bar[0])
            #for x in range(5, 15):
            #    for y in range(6, 12):
            #        draw_block(x, y, Map[y][x].mtype) # 这个东西会导致炸弹显示不出来!!!!!!!!!!!!!!!!!!
            if MAIN_TPS > Title_Bar[1]:
                Title_Bar[0] = None
                #Redraw()
        
        MovBombNeo(pl, e1, e2, e3)
        #for x in range(0, 20):
        #    for y in range(0, 15):
        #        mods.Iris. draw_addon(x, y, Map[y][x].addon)
        Shaders.quickdraw.DrawAddons(Map)
        if TNTList != []: # //20240103 改变渲染顺序
            for t in TNTList:
                mods.Iris.draw_item(t)
        if len(RocketList) != 0:
            for r in RocketList:
                mods.Iris.draw_item(r)
        power_max = max(
            pl.power if pl.health > 0 else 2,
            e1.power if e1.health > 0 else 2,
            e2.power if e2.health > 0 else 2,
            e3.power if e3.health > 0 else 2
        )
        if len(MineList) != 0:
            MoveMine((pl, e1, e2, e3))
            for lm in MineList:
                mods.Iris.draw_item(lm)
        if MAIN_TPS >= e1.nextTps:
            e1.run(pl, power_max, None, e3)
        if e1.health > 0:
            mods.Iris.draw_entity(e1, screen)
            if e1.ender_pearl_timeout != 0:
                mods.Iris.draw_particle_enderpearl(e1.x * 60 + e1.xp, e1.y * 60 + e1.yp)
        if MAIN_TPS >= e2.nextTps:
            e2.run(pl, power_max, None, e3)
        if e2.health > 0:
            mods.Iris.draw_entity(e2, screen)
            if e1.ender_pearl_timeout != 0:
                mods.Iris.draw_particle_enderpearl(e1.x * 60 + e1.xp, e1.y * 60 + e1.yp)
        if MAIN_TPS >= e3.nextTps:
            e3.run(pl, power_max, None, e2)
        if e3.health > 0:
            mods.Iris.draw_entity(e3, screen)
            if e1.ender_pearl_timeout != 0:
                mods.Iris.draw_particle_enderpearl(e1.x * 60 + e1.xp, e1.y * 60 + e1.yp)
        if first_run and not mods.cfg.SimplifyOutputInformation:
            printm('获取NPC信息。')
        if pl.health > 0:
            mods.Iris.draw_entity(pl, screen)
        if first_run == True:
            if not mods.cfg.SimplifyOutputInformation:
                printm('绘制完成。')
            printm('循环检查完成。')
            first_run = False
        Monitor.greet()
        pygame.display.update()
'''
        
/*  *********  ********  ******   小游戏   ******  ********  *********  */
use dict to pass msg.


feature: dict = {
    "enemy_invisible": int enemy初始隐身时间, -1->inf, 0->disabled, int->ltd_time,
    "player_bed": bool,
    "enemy_bed": bool,
    "reload_map": bool,
    "tick_operate": {
        tick_time<int>: fn<Callable<pl, e1, e2, e3>>
    },
    "requirements": fn<Callable(stats: dict)>,
    "before": Callable, # if return (1, ...), exit
}

requirements = {
    "player_hurt": int,
    "enemy_hurt": int,
    "max_time": int 最大用时
}
'''

# helperclass
class qb_ticker(object):
    def __init__(self, _tick: int, _callable: typing.Callable | None = None):
        self.TICK: int = _tick
        self.tick: int = ftps() + self.TICK
        self.fn  : typing.Callable | None = _callable
        assert(self.TICK > 0)
    def check_tick(self, tps: int) -> bool:
        if tps >= self.tick:
            self.tick = tps + self.TICK
            return True
        else:
            return False
    def __pass(self):
        pass
    def auto(self, tps: int, *args) -> None:
        if self.check_tick(tps):
            if self.fn != None:
                self.fn(*args)
            else:
                self.__pass()

# core
def template(feature: dict):
    global screen, Map, first_run, blist, TNTList, icelist, RocketList, GB_LEVEL_SHUT, MineList
    mods.Iris.on_NewGame()
    if "before" in feature.keys():
        before_fnret = feature['before']()
        if type(before_fnret) == tuple:
            return before_fnret[-1]
    if feature.get('reload_map', True):
        reLoad()
        printm('地图初始化完成。')
    blist = []
    TNTList = []
    icelist = []
    RocketList = []
    MineList = []
    #if not mods.cfg.SimplifyOutputInformation:
    #    printm('修改重生点信息。')
    if not feature.get('enemy_bed', False):
        Map[3][3].mtype = Void
    if not feature.get('player_bed', False):
        Map[11][16].mtype = Void
    for x in range(15, 18):
        for y in range(2, 5):
            Map[y][x].mtype = Void
    for x in range(2, 5):
        for y in range(10, 13):
            Map[y][x].mtype = Void
    for row in range(15):
        for col in range(20):
            draw_block(col, row, Map[row][col].mtype)
    if not mods.cfg.SimplifyOutputInformation:
        printm('显示地图完毕。')
    e1, e2, e3, pl = Enemy(1), Enemy(2), Enemy(3), Player(0)
    pl.capacity = 1
    e1.capacity = 1
    e2.capacity = 1
    e3.capacity = 1
    enemy_invisible_time: int = feature.get('enemy_invisible', 0)
    if enemy_invisible_time > 0:
        enemy_invisible_time += ftps()
    if not mods.cfg.SimplifyOutputInformation:
        printm('实体信息加载完毕。')
    #enemy_lived = 3
    #power_max = 2 if mods.cfg.PowerIncrease == True else 3
    power_max : int = 2
    
    qb_ticker_list: list[qb_ticker] = []
    if "tick_operate" in feature.keys():
        #assert(isinstance(feature['tick_operate']) == dict)
        for fto_key, fto_value in feature["tick_operate"].items():
            qb_ticker_list.append(
                qb_ticker(fto_key, fto_value)
            )
    
    
    Title_Bar = [None, 0] # 显示在屏幕中心的文字。用于提示事件。格式: [what:Union[None, str], tps_add:int]
    Player_bed_has_been_destroyed = True
    
    MovTNT_TPS = ftps()
    MovIceThornTps = ftps() + 50
    FPS_Tick = ftps() + 10
    MovRocketTPS = ftps() + 20
    New_Rocket_List = []
    #first_run = True
    fps_get_now = 0
    Time_Start = ftps()
    MAIN_TPS : int = ftps() # 1.12.2
    ShowFPSTime = MAIN_TPS
    
    # *** *** Normal & Level Only
    TimeScoreTPS : int = MAIN_TPS
    if GB_NOW == GB_LEVEL:
        ST_LEVELGOALS.timescore = [0,0,0,0]
        ST_LEVELGOALS.scores = [0,0,0,0]
    while True:
        fps.tick(mods.cfg.FPS_limit)
        MAIN_TPS = ftps()
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                GB_LEVEL_SHUT = True
                raise ExitGame('Click quit button.')
        
        if Map[11][16].mtype != Bed and Player_bed_has_been_destroyed == False:
            Title_Bar[0] = '重生点已被摧毁'
            Title_Bar[1] = MAIN_TPS + 3_800
            Player_bed_has_been_destroyed = True
        if Map[3][3].mtype != Bed and e1.health<=0 and e2.health<=0 and e3.health <=0 and not e1.shield and not e2.shield and not e3.shield:
            printm('Player win.')
            if GB_NOW == GB_LEVEL:
                for i in range(4):
                    ST_LEVELGOALS.totalscore[i] += ST_LEVELGOALS.timescore[i]
                    ST_LEVELGOALS.totalscore[i] += ST_LEVELGOALS.scores[i]
            Time_Used = MAIN_TPS - Time_Start
            textWin = f.render(f'You Win! Use time: {Time_Used/1000:5.1f}', True, (255, 80, 80))
            screen.blit(textWin, (1220, 600))
            mods.printlib.xyprint('       ~ ~ ~ You Win! ~ ~ ~', color = (0, 255, 100), place = ('center', 8))
            pygame.display.flip()
            pygame.time.wait(3_000)
            #raise ExitGame('Game finish.')
            return 1
        if Map[11][16].mtype != Bed and pl.health <= 0 and not pl.shield:
            printm('Player lose.')
            if GB_NOW == GB_LEVEL:
                ST_LEVELGOALS.scores[3] += 3000
                ST_LEVELGOALS.scores[2] += 3000
                ST_LEVELGOALS.scores[1] += 3000
                for i in range(4):
                    ST_LEVELGOALS.totalscore[i] += ST_LEVELGOALS.timescore[i]
                    ST_LEVELGOALS.totalscore[i] += ST_LEVELGOALS.scores[i]
            Time_Used = MAIN_TPS - Time_Start
            textLose = f.render(f'You lose. Use time: {Time_Used/1000:5.1f}', True, (80, 80, 255))
            screen.blit(textLose, (1220, 600))
            mods.printlib.xyprint('       - _ - You lose. - _ -', color = (0, 255, 100), place = ('center', 8))
            pygame.display.flip()
            pygame.time.wait(3_000)
            #raise ExitGame('Game over.')
            return 0
        
        
        #fps_get_now = fps.get_fps()
        if MAIN_TPS >= FPS_Tick:
            fps_get_now = fps.get_fps()
            pygame.draw.rect(screen, (0,0,0), (1201, 0, 298, 900))
            if mods.cfg.SimplifyDebuggingInformation == True:
                Text1 = f.render(f'FPS: {fps_get_now:3.2f} limit:{"垂直同步" if mods.cfg.FPS_limit == 60 else mods.cfg.FPS_limit if mods.cfg.FPS_limit <= 1000 else "无限制"} power: {power_max}', True, (255, 0 ,0))
                screen.blit(Text1, (1220, 20))
                Text_player = f.render(f'({pl.goal})Player: ({pl.x},{pl.y}) {"live" if pl.health>0 else "died"} kill: {pl.kill} died: {pl.died}', True, (0,255,0))
                screen.blit(Text_player, (1201, 50))
                Text_e1 = f.render(f'({e1.goal})Enemy1: ({e1.x},{e1.y}) {"live" if e1.health>0 else "died"} kill: {e1.kill} died: {e1.died}', True, (0, 255, 0))
                screen.blit(Text_e1, (1201, 80))
                Text_e2 = f.render(f'({e2.goal})Enemy2: ({e2.x},{e2.y}) {"live" if e2.health>0 else "died"} kill: {e2.kill} died: {e2.died}', True, (0, 255, 0))
                screen.blit(Text_e2, (1201, 110))
                Text_e3 = f.render(f'({e3.goal})Enemy3: ({e3.x},{e3.y}) {"live" if e3.health>0 else "died"} kill: {e3.kill} died: {e3.died}', True, (0, 255, 0))
                screen.blit(Text_e3, (1201, 140))
            else:
                mods.printlib.OutTextxy(f'FPS: {fps_get_now:3.2f} limit:{"垂直同步" if mods.cfg.EnableVerticalSynchronization == True else mods.cfg.FPS_limit if mods.cfg.FPS_limit <= 1000 else "无限制"} power: {power_max}', (255,0,0), ('right', 0))
                mods.printlib.OutTextxy(f'Player:  Position: ({pl.x},{pl.y})  {"live" if pl.health>0 else "died"}', (0, 255, 255), ('right', 1))
                mods.printlib.OutTextxy(f'    Goal:{pl.goal} Kill:{pl.kill} Death:{pl.died} Mine:{pl.hold_mine}', (0,255,255), ('right', 2))
                mods.printlib.OutTextxy(f'    Capacity:{pl.capacity} TNT:{pl.hold_tnt} Power:{pl.power} Rocket:{pl.rocket}', (0,255,255), ('right', 3))
                mods.printlib.OutTextxy(f'Enemy1:  Position: ({e1.x},{e1.y})  {"live" if e1.health>0 else "died"}', mods.cfg.color_e1, ('right', 4))
                mods.printlib.OutTextxy(f'    Goal:{e1.goal} Kill:{e1.kill} Death:{e1.died} Mine:{e1.hold_mine}', mods.cfg.color_e1, ('right', 5))
                mods.printlib.OutTextxy(f'    Capacity:{e1.capacity} TNT:{e1.hold_tnt} Power:{e1.power} Rocket:{e1.rocket}', mods.cfg.color_e1, ('right', 6))
                mods.printlib.OutTextxy(f'Enemy2:  Position: ({e2.x},{e2.y})  {"live" if e2.health>0 else "died"}', mods.cfg.color_e2, ('right', 7))
                mods.printlib.OutTextxy(f'    Goal:{e2.goal} Kill:{e2.kill} Death:{e2.died} Mine:{e2.hold_mine}', mods.cfg.color_e2, ('right', 8))
                mods.printlib.OutTextxy(f'    Capacity:{e2.capacity} TNT:{e2.hold_tnt} Power:{e2.power} Rocket:{e2.rocket}', mods.cfg.color_e2, ('right', 9))
                mods.printlib.OutTextxy(f'Enemy3:  Position: ({e3.x},{e3.y})  {"live" if e3.health>0 else "died"}', mods.cfg.color_e3, ('right', 10))
                mods.printlib.OutTextxy(f'    Goal:{e3.goal} Kill:{e3.kill} Death:{e3.died} Mine:{e3.hold_mine}', mods.cfg.color_e3, ('right', 11))
                mods.printlib.OutTextxy(f'    Capacity:{e3.capacity} TNT:{e3.hold_tnt} Power:{e3.power} Rocket:{e3.rocket}', mods.cfg.color_e3, ('right', 12))
                
                # 状态指示器
                if pl.shield:
                    mods.Iris.draw_image('effect_shield', (1469, 2 * 30 + 10))
                if pl.slow >= 0:
                    mods.Iris.draw_image('effect_slow', (1469, 3 * 30 + 10))
                if e1.shield:
                    mods.Iris.draw_image('effect_shield', (1469, 5 * 30 + 10))
                if e1.slow >= 0:
                    mods.Iris.draw_image('effect_slow', (1469, 6 * 30 + 10))
                if e2.shield:
                    mods.Iris.draw_image('effect_shield', (1469, 8 * 30 + 10))
                if e2.slow >= 0:
                    mods.Iris.draw_image('effect_slow', (1469, 9 * 30 + 10))
                if e3.shield:
                    mods.Iris.draw_image('effect_shield', (1469, 11 * 30 + 10))
                if e3.slow >= 0:
                    mods.Iris.draw_image('effect_slow', (1469, 12 * 30 + 10))
                
                # Scores in level mode
                if GB_NOW == GB_LEVEL:
                    mods.printlib.OutTextxy(
                        f"E0: {ST_LEVELGOALS.scores[0]} E1: {ST_LEVELGOALS.scores[1]} E2: {ST_LEVELGOALS.scores[2]} E3: {ST_LEVELGOALS.scores[3]}",
                        (8, 255, 169), ('right', 14)
                    )
                    #mods.printlib.OutTextxy(
                    #    f"Time score: <{ST_LEVELGOALS.timescore[0]}, {ST_LEVELGOALS.timescore[1]}, {ST_LEVELGOALS.timescore[2]}, {ST_LEVELGOALS.timescore[3]}>",
                    #    (8, 255, 169), ('right', 15)
                    #)
                    mods.printlib.OutTextxy(
                        f"Lvl: {GB_LEVEL_LVL} / 25", (8, 120, 255), ('right', 15)
                    )
                mods.printlib.xyprint(f'Facing: {pl.facing}', (255, 255, 255), ('right', 12))
                
                #mods.printlib.xyprint(f'CPU: {cpu_per}  Memory: {mem_per}', (0, 255, 0), ('right', 17))
            Time_Used = MAIN_TPS - Time_Start
            mods.printlib.xyprint(f'Time:{Time_Used / 1000:.2f} sec', (128, 196, 64), ('right', 13))
            FPS_Tick = MAIN_TPS + 100
        
        if first_run == True and not mods.cfg.SimplifyOutputInformation:
            printm('01 judge finished.')
        
        kp = pygame.key.get_pressed()
        if first_run == True and not mods.cfg.SimplifyOutputInformation:
            printm('获取按键信息。')
        if kp[pygame.K_p]:
            mods.CrystalLib.Pause()
            Redraw()
        
        if MAIN_TPS > MovTNT_TPS:
            MovTNT(pl, e1, e2, e3)
            MovTNT_TPS = MAIN_TPS + 50
        if MAIN_TPS > MovRocketTPS:
            MovRocketTPS = MAIN_TPS + 20
            if len(RocketList) != 0:
                New_Rocket_List = []
                for r in RocketList:
                    MovRocket(r, pl, e1, e2, e3)
                    if r.live == True:
                        New_Rocket_List.append(r)
                    else:
                        pass
                RocketList = New_Rocket_List
        # 1.12.2
        if MAIN_TPS > TimeScoreTPS:
            TimeScoreTPS = MAIN_TPS + 169
            if GB_NOW == GB_LEVEL:
                for e in ((pl, 0),  (e1, 1),  (e2, 2),  (e3, 3)):
                    if e[0].health > 0:
                        ST_LEVELGOALS.timescore[e[1]] += 1
        if MAIN_TPS>= pl.nextTps:
            if pl.health <= 0:
                if pl.rebirth < 0:
                    pl.rebirth = 480 if pl.shield == False else 1
                    pl.died += 1
                    #pl.goal = 0
                elif pl.rebirth == 0:
                    pl.x = 16
                    pl.y = 11
                    pl.health = 1
                    pl.rebirth = -1
                    pl.xp = 0
                    pl.yp = 0
                    pl.using_b = False
                    if not pl.shield:
                        pl.goal = 0
                        pl.frozen = False
                        pl.slow = -1
                        pl.speed = mods.cfg.player_speed()
                    else:
                        pl.shield = False
                else:
                    pl.rebirth -= 1
            
            if pl.xp > 0:
                pl.xp += pl.speed
                if pl.xp > 60:
                    pl.xp = 0
                    pl.x += 1
            elif pl.yp > 0:
                pl.yp += pl.speed
                if pl.yp > 60:
                    pl.yp = 0
                    pl.y += 1
            elif pl.xp < 0:
                pl.xp -= pl.speed
                if pl.xp < -60:
                    pl.xp = 0
                    pl.x -= 1
            elif pl.yp < 0:
                pl.yp -= pl.speed
                if pl.yp < -60:
                    pl.yp = 0
                    pl.y -= 1
            else:
                if kp[pygame.K_UP] and cango(pl, 'north') and pl.health > 0 and not pl.frozen:
                    pl.yp = -1
                elif kp[pygame.K_DOWN] and cango(pl, 'south') and pl.health > 0 and not pl.frozen:
                    pl.yp = 1
                elif kp[pygame.K_LEFT] and cango(pl, 'west') and pl.health > 0 and not pl.frozen:
                    pl.xp = -1
                elif kp[pygame.K_RIGHT] and cango(pl, 'east') and pl.health > 0 and not pl.frozen:
                    pl.xp = 1
            if pl.health > 0 and not pl.frozen:
                if kp[pygame.K_UP]:
                    pl.facing = 'north'
                elif kp[pygame.K_DOWN]:
                    pl.facing = 'south'
                elif kp[pygame.K_LEFT]:
                    pl.facing = 'west'
                elif kp[pygame.K_RIGHT]:
                    pl.facing = 'east'
            
            if pl.ender_pearl_timeout > 0:
                pl.ender_pearl_timeout -= 1
            if kp[pygame.K_w] and pl.ender_pearl_timeout <= 0 and pl.goal>=2 and pl.health > 0:
                pl.ender_pearl_timeout = 200
                pl.y -= mods.cfg.item_ender_pearl_distance()
                if pl.y < 0:
                    pl.y = 0
                pl.goal -= 2
            if kp[pygame.K_a] and pl.ender_pearl_timeout <= 0 and pl.goal>=2 and pl.health > 0:
                pl.ender_pearl_timeout = 200
                pl.x -= mods.cfg.item_ender_pearl_distance()
                if pl.x < 0:
                    pl.x = 0
                pl.goal -= 2
            if kp[pygame.K_s] and pl.ender_pearl_timeout <= 0 and pl.goal>=2 and pl.health > 0:
                pl.ender_pearl_timeout = 200
                pl.y += mods.cfg.item_ender_pearl_distance()
                if pl.y > 14:
                    pl.y = 14
                pl.goal -= 2
            if kp[pygame.K_d] and pl.ender_pearl_timeout <= 0 and pl.goal>=2 and pl.health > 0:
                pl.ender_pearl_timeout = 200
                pl.x += mods.cfg.item_ender_pearl_distance()
                if pl.x > 19:
                    pl.x = 19
                pl.goal -= 2
            
            if pl.tnt_timeout > 0:
                pl.tnt_timeout -= 1
            if pl.rocket_timeout > 0:
                pl.rocket_timeout -= 1
            if kp[pygame.K_t] and pl.tnt_timeout <= 0 and pl.hold_tnt >0 and pl.health > 0 and not pl.frozen:
                pl.tnt_timeout = 40
                if pl.facing == 'west':
                    Create_TNT(TNT(
                        y = pl.y, x = max(0, pl.x - mods.cfg.TNTPower), belong = 0,Time=MAIN_TPS+1_600,
                        xFrom=pl.x, yFrom=pl.y
                    ))
                    pl.hold_tnt -= 1
                elif pl.facing == 'east':
                    Create_TNT(TNT(
                        y = pl.y, x = min(19, pl.x + mods.cfg.TNTPower), belong = 0,Time=MAIN_TPS+1_600,
                        xFrom=pl.x, yFrom=pl.y
                    ))
                    pl.hold_tnt -= 1
                elif pl.facing == 'south':
                    Create_TNT(TNT(
                        x = pl.x, y = min(14, pl.y + mods.cfg.TNTPower), belong = 0,Time=MAIN_TPS+1_600,
                        xFrom=pl.x, yFrom=pl.y
                    ))
                    pl.hold_tnt -= 1
                else:
                    Create_TNT(TNT(
                        x = pl.x, y = max(0, pl.y - mods.cfg.TNTPower), belong = 0,Time=MAIN_TPS+1_600,
                        xFrom=pl.x, yFrom=pl.y
                    ))
                    pl.hold_tnt -= 1
            
            if kp[pygame.K_r] and pl.rocket_timeout <= 0 and pl.rocket > 0 and pl.health > 0 and not pl.frozen:
                pl.rocket_timeout = 32
                AddRocket(
                    pl.x, pl.y, pl.facing, pl.ID
                )
                pl.rocket -= 1
            if kp[pygame.K_m] and pl.hold_mine > 0 and pl.health > 0 and not pl.frozen and HaveMine(pl.x, pl.y)==False:
                Try_SetLandMine(pl)
            if pl.ice_timeout > 0:
                pl.ice_timeout -= 1
            if kp[pygame.K_i] and pl.ice_timeout <= 0 and pl.goal >= 1 and pl.health > 0 and not pl.frozen:
                pl.ice_timeout = 70
                xn, yn = pl.x, pl.y
                if pl.facing == 'east':
                    xn += 1
                elif pl.facing == 'south':
                    yn += 1
                elif pl.facing == 'west':
                    xn -= 1
                else:
                    yn -= 1
                if 0 <= xn <= 19 and 0 <= yn <= 14:
                    icelist.append(IceThorn(x = xn, y = yn, direct = pl.facing, Time = 0))
            
            if kp[pygame.K_SPACE] and pl.capacity > 0 and not hasb(pl.x, pl.y) and pl.health > 0 and Map[pl.y][pl.x].mtype == Void and not pl.frozen:
                CreateBomb(Bomb(x = pl.x, y = pl.y, Time = MAIN_TPS + 2_200, belong = 0, color = (50, 255, 255), power = pl.power))
                pl.capacity -= 1
            #if kp[pygame.K_b] and Map[pl.y][pl.x].mtype == Void and pl.health > 0 and not pl.frozen:
            #    Map[pl.y][pl.x].mtype = Soft
            
            pl.nextTps = MAIN_TPS + 10
            
            # 20240103
            #eat
            match Map[pl.y][pl.x].addon:
                case mods.Iris.MA_NULL:
                    pass
                case mods.Iris.MA_APPLE:
                    pl.capacity += 1
                    Map[pl.y][pl.x].addon = mods.Iris.MA_NULL
                    if GB_NOW == GB_LEVEL:
                        ST_LEVELGOALS.scores[pl.ID] += 20
                case mods.Iris.MA_POWER:
                    pl.power += 1
                    Map[pl.y][pl.x].addon = mods.Iris.MA_NULL
                    if GB_NOW == GB_LEVEL:
                        ST_LEVELGOALS.scores[pl.ID] += 20
                case mods.Iris.MA_TNT:
                    pl.hold_tnt += 1
                    Map[pl.y][pl.x].addon = mods.Iris.MA_NULL
                    if GB_NOW == GB_LEVEL:
                        ST_LEVELGOALS.scores[pl.ID] += 20
                case mods.Iris.MA_SHIELD:
                    pl.shield = True
                    Map[pl.y][pl.x].addon = mods.Iris.MA_NULL
                    if GB_NOW == GB_LEVEL:
                        ST_LEVELGOALS.scores[pl.ID] += 20
                    
                case mods.Iris.MA_ROCKET:
                    pl.rocket += 1
                    Map[pl.y][pl.x].addon = mods.Iris.MA_NULL
                    if GB_NOW == GB_LEVEL:
                        ST_LEVELGOALS.scores[pl.ID] += 20
                case mods.Iris.MA_DIAMOND:
                    Map[pl.y][pl.x].addon = mods.Iris.MA_NULL
                    if GB_NOW == GB_LEVEL:
                        ST_LEVELGOALS.scores[pl.ID] += 100
                case mods.Iris.MA_LANDMINE:
                    pl.hold_mine += 1
                    Map[pl.y][pl.x].addon = mods.Iris.MA_NULL
                    if GB_NOW == GB_LEVEL:
                        ST_LEVELGOALS.scores[pl.ID] += 20
                case _:
                    pass
        if first_run and not mods.cfg.SimplifyOutputInformation:
            printm('获取Player操作信息。')
        
        #
        
        # 渲染顺序：
        # entity_around
        # MovBomb (Bomb_around + Bomb + bbs)
        # entity
        
        #
        
        # New (slower, but no bugs?)
        #for x in range(0, 20):
        #    for y in range(0, 15):
        #        draw_block(x, y, Map[y][x].mtype)
        Shaders.quickdraw.DrawBlocks(Map)
        
        # In new render plan, there's no need forus to render entities's env anymore!
        #MovBomb(pl, e1, e2, e3, isOnlyRender = True)
        if MAIN_TPS >= MovIceThornTps:
            movIceThorn(tuple([pl, e1, e2, e3]), False)
            MovIceThornTps = MAIN_TPS + 50
        else:
            movIceThorn(tuple([pl, e1, e2, e3]), isOnlyRender = True)
        pl.mov_effect()
        if pl.ender_pearl_timeout != 0:
            mods.Iris.draw_particle_enderpearl(pl.x * 60 + pl.xp, pl.y * 60 + pl.yp)
        #if TNTList != []: //20240103 改变渲染顺序
        #    for t in TNTList:
        #        mods.Iris.draw_item(t)
        
        if first_run == True and not mods.cfg.SimplifyOutputInformation:
            printm('地图更新完成。')
        
        if Title_Bar[0] != None:
            mods.printlib.TitleBarPrint(Title_Bar[0])
            #for x in range(5, 15):
            #    for y in range(6, 12):
            #        draw_block(x, y, Map[y][x].mtype) # 这个东西会导致炸弹显示不出来!!!!!!!!!!!!!!!!!!
            if MAIN_TPS > Title_Bar[1]:
                Title_Bar[0] = None
                #Redraw()
        
        MovBombNeo(pl, e1, e2, e3)
        #for x in range(0, 20):
        #    for y in range(0, 15):
        #        mods.Iris. draw_addon(x, y, Map[y][x].addon)
        Shaders.quickdraw.DrawAddons(Map)
        if TNTList != []: # //20240103 改变渲染顺序
            for t in TNTList:
                mods.Iris.draw_item(t)
        if len(RocketList) != 0:
            for r in RocketList:
                mods.Iris.draw_item(r)
        power_max = max(
            pl.power if pl.health > 0 else 2,
            e1.power if e1.health > 0 else 2,
            e2.power if e2.health > 0 else 2,
            e3.power if e3.health > 0 else 2
        )
        if len(MineList) != 0:
            MoveMine((pl, e1, e2, e3))
            for lm in MineList:
                mods.Iris.draw_item(lm)
        if MAIN_TPS >= e1.nextTps:
            e1.run(pl, power_max, None, e3)
        if e1.health > 0:
            if enemy_invisible_time < 0 or MAIN_TPS < enemy_invisible_time:
                pass
            else:
                mods.Iris.draw_entity(e1, screen)
            if e1.ender_pearl_timeout != 0:
                mods.Iris.draw_particle_enderpearl(e1.x * 60 + e1.xp, e1.y * 60 + e1.yp)
        if MAIN_TPS >= e2.nextTps:
            e2.run(pl, power_max, None, e3)
        if e2.health > 0:
            if enemy_invisible_time < 0 or MAIN_TPS < enemy_invisible_time:
                pass
            else:
                mods.Iris.draw_entity(e2, screen)
            if e1.ender_pearl_timeout != 0:
                mods.Iris.draw_particle_enderpearl(e1.x * 60 + e1.xp, e1.y * 60 + e1.yp)
        if MAIN_TPS >= e3.nextTps:
            e3.run(pl, power_max, None, e2)
        if e3.health > 0:
            if enemy_invisible_time < 0 or MAIN_TPS < enemy_invisible_time:
                pass
            else:
                mods.Iris.draw_entity(e3, screen)
            if e1.ender_pearl_timeout != 0:
                mods.Iris.draw_particle_enderpearl(e1.x * 60 + e1.xp, e1.y * 60 + e1.yp)
        if first_run and not mods.cfg.SimplifyOutputInformation:
            printm('获取NPC信息。')
        if pl.health > 0:
            mods.Iris.draw_entity(pl, screen)
        if first_run == True:
            if not mods.cfg.SimplifyOutputInformation:
                printm('绘制完成。')
            printm('循环检查完成。')
            first_run = False
        if len(qb_ticker_list) != 0:
            for fto in qb_ticker_list:
                fto.auto(MAIN_TPS, pl, e1, e2, e3)
        pygame.display.update()


def Double_Normal(isPVP: bool):
    global screen, Map, first_run, blist, TNTList, icelist, RocketList, GB_LEVEL_SHUT, MineList
    mods.Iris.on_NewGame()
    blist = []
    TNTList = []
    icelist = []
    RocketList = []
    MineList = []
    if not mods.cfg.SimplifyOutputInformation:
        printm('修改重生点信息。')
    Map[3][3].mtype = Void
    Map[11][16].mtype = Void
    for x in range(15, 18):
        for y in range(2, 5):
            Map[y][x].mtype = Void
    for x in range(2, 5):
        for y in range(10, 13):
            Map[y][x].mtype = Void
    for row in range(15):
        for col in range(20):
            draw_block(col, row, Map[row][col].mtype)
    if not mods.cfg.SimplifyOutputInformation:
        printm('显示地图完毕。')
    e1, e2, e3, pl = Player(1), Enemy(2), Enemy(3), Player(0)
    pl.capacity = 1
    e1.capacity = 1
    e2.capacity = 1
    e3.capacity = 1
    e1.x = 3
    e1.y = 3
    if isPVP == True:
        e2.health = 0
        e3.health = 0
    if not mods.cfg.SimplifyOutputInformation:
        printm('实体信息加载完毕。')
    #enemy_lived = 3
    #power_max = 2 if mods.cfg.PowerIncrease == True else 3
    power_max : int = 2
    
    Title_Bar = [None, 0] # 显示在屏幕中心的文字。用于提示事件。格式: [what:Union[None, str], tps_add:int]
    Player_bed_has_been_destroyed = True
    
    MovTNT_TPS = ftps()
    MovIceThornTps = ftps() + 50
    FPS_Tick = ftps() + 10
    Mov_Rocket_TPS = ftps() + 20
    #first_run = True
    fps_get_now = 0
    Time_Start = ftps()
    MAIN_TPS : int = ftps()
    while True:
        fps.tick(mods.cfg.FPS_limit)
        MAIN_TPS = ftps()
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                GB_LEVEL_SHUT = True
                raise ExitGame('Click quit button.')
        
        if Map[11][16].mtype != Bed and Player_bed_has_been_destroyed == False:
            Title_Bar[0] = '重生点已被摧毁'
            Title_Bar[1] = MAIN_TPS + 3_800
            Player_bed_has_been_destroyed = True
        if Map[3][3].mtype != Bed and  e2.health<=0 and e3.health <=0  and not e2.shield and not e3.shield:
            if isPVP == False:
                printm('Player win.')
                Time_Used = MAIN_TPS - Time_Start
                textWin = f.render(f'You Win! Use time: {Time_Used/1000:5.1f}', True, (255, 80, 80))
                screen.blit(textWin, (1220, 600))
                mods.printlib.xyprint('       ~ ~ ~ You Win! ~ ~ ~', color = (0, 255, 100), place = ('center', 8))
                pygame.display.flip()
                pygame.time.wait(3_000)
                raise ExitGame('Game finish.')
            else:
                if pl.HasAlreadyDied == True:
                    printm('Player1 win.')
                    Time_Used = MAIN_TPS - Time_Start
                    textWin = f.render(f'P1 Win! Use time: {Time_Used/1000:5.1f}', True, (255, 80, 80))
                    screen.blit(textWin, (1220, 600))
                    mods.printlib.xyprint('       ~ ~ ~ P1 Win! ~ ~ ~', color = (0, 255, 100), place = ('center', 8))
                    pygame.display.flip()
                    pygame.time.wait(3_000)
                    raise ExitGame('Game finish.')
                elif e1.HasAlreadyDied == True:
                    printm('Player2 win.')
                    Time_Used = MAIN_TPS - Time_Start
                    textWin = f.render(f'P2 Win! Use time: {Time_Used/1000:5.1f}', True, (255, 80, 80))
                    screen.blit(textWin, (1220, 600))
                    mods.printlib.xyprint('       ~ ~ ~ P2 Win! ~ ~ ~', color = (0, 255, 100), place = ('center', 8))
                    pygame.display.flip()
                    pygame.time.wait(3_000)
                    raise ExitGame('Game finish.')
        if Map[11][16].mtype != Bed and pl.health <= 0 and e1.health <= 0 and not e1.shield and not pl.shield:
            printm('Player lose.')
            Time_Used = MAIN_TPS - Time_Start
            textLose = f.render(f'You lose. Use time: {Time_Used/1000:5.1f}', True, (80, 80, 255))
            screen.blit(textLose, (1220, 600))
            mods.printlib.xyprint('       - _ - You lose. - _ -', color = (0, 255, 100), place = ('center', 8))
            pygame.display.flip()
            pygame.time.wait(3_000)
            raise ExitGame('Game over.')
        
        
        
        #fps_get_now = fps.get_fps()
        if MAIN_TPS >= FPS_Tick:
            fps_get_now = fps.get_fps()
            pygame.draw.rect(screen, (0,0,0), (1201, 0, 298, 900))
            if mods.cfg.SimplifyDebuggingInformation == True:
                Text1 = f.render(f'FPS: {fps_get_now:3.2f} limit:{"垂直同步" if mods.cfg.FPS_limit == 60 else mods.cfg.FPS_limit if mods.cfg.FPS_limit <= 1000 else "无限制"} power: {power_max}', True, (255, 0 ,0))
                screen.blit(Text1, (1220, 20))
                Text_player = f.render(f'({pl.goal})Player: ({pl.x},{pl.y}) {"live" if pl.health>0 else "died"} kill: {pl.kill} died: {pl.died}', True, (0,255,0))
                screen.blit(Text_player, (1201, 50))
                Text_e1 = f.render(f'({e1.goal})Enemy1: ({e1.x},{e1.y}) {"live" if e1.health>0 else "died"} kill: {e1.kill} died: {e1.died}', True, (0, 255, 0))
                screen.blit(Text_e1, (1201, 80))
                Text_e2 = f.render(f'({e2.goal})Enemy2: ({e2.x},{e2.y}) {"live" if e2.health>0 else "died"} kill: {e2.kill} died: {e2.died}', True, (0, 255, 0))
                screen.blit(Text_e2, (1201, 110))
                Text_e3 = f.render(f'({e3.goal})Enemy3: ({e3.x},{e3.y}) {"live" if e3.health>0 else "died"} kill: {e3.kill} died: {e3.died}', True, (0, 255, 0))
                screen.blit(Text_e3, (1201, 140))
            else:
                mods.printlib.OutTextxy(f'FPS: {fps_get_now:3.2f} limit:{"垂直同步" if mods.cfg.EnableVerticalSynchronization == True else mods.cfg.FPS_limit if mods.cfg.FPS_limit <= 1000 else "无限制"} power: {power_max}', (255,0,0), ('right', 0))
                mods.printlib.OutTextxy(f'Player:  Position: ({pl.x},{pl.y})  {"live" if pl.health>0 else "died"}', (0, 255, 255), ('right', 1))
                mods.printlib.OutTextxy(f'    Goal:{pl.goal} Kill:{pl.kill} Death:{pl.died} Mine:{pl.hold_mine}', (0,255,255), ('right', 2))
                mods.printlib.OutTextxy(f'    Capacity:{pl.capacity} TNT:{pl.hold_tnt} Power:{pl.power} Rocket:{pl.rocket}', (0,255,255), ('right', 3))
                mods.printlib.OutTextxy(f'Enemy1:  Position: ({e1.x},{e1.y})  {"live" if e1.health>0 else "died"}', mods.cfg.color_e1, ('right', 4))
                mods.printlib.OutTextxy(f'    Goal:{e1.goal} Kill:{e1.kill} Death:{e1.died} Mine:{e1.hold_mine}', mods.cfg.color_e1, ('right', 5))
                mods.printlib.OutTextxy(f'    Capacity:{e1.capacity} TNT:{e1.hold_tnt} Power:{e1.power} Rocket:{e1.rocket}', mods.cfg.color_e1, ('right', 6))
                mods.printlib.OutTextxy(f'Enemy2:  Position: ({e2.x},{e2.y})  {"live" if e2.health>0 else "died"}', mods.cfg.color_e2, ('right', 7))
                mods.printlib.OutTextxy(f'    Goal:{e2.goal} Kill:{e2.kill} Death:{e2.died} Mine:{e2.hold_mine}', mods.cfg.color_e2, ('right', 8))
                mods.printlib.OutTextxy(f'    Capacity:{e2.capacity} TNT:{e2.hold_tnt} Power:{e2.power} Rocket:{e2.rocket}', mods.cfg.color_e2, ('right', 9))
                mods.printlib.OutTextxy(f'Enemy3:  Position: ({e3.x},{e3.y})  {"live" if e3.health>0 else "died"}', mods.cfg.color_e3, ('right', 10))
                mods.printlib.OutTextxy(f'    Goal:{e3.goal} Kill:{e3.kill} Death:{e3.died} Mine:{e3.hold_mine}', mods.cfg.color_e3, ('right', 11))
                mods.printlib.OutTextxy(f'    Capacity:{e3.capacity} TNT:{e3.hold_tnt} Power:{e3.power} Rocket:{e3.rocket}', mods.cfg.color_e3, ('right', 12))
                
                # 状态指示器
                if pl.shield:
                    mods.Iris.draw_image('effect_shield', (1469, 2 * 30 + 10))
                if pl.slow >= 0:
                    mods.Iris.draw_image('effect_slow', (1469, 3 * 30 + 10))
                if e1.shield:
                    mods.Iris.draw_image('effect_shield', (1469, 5 * 30 + 10))
                if e1.slow >= 0:
                    mods.Iris.draw_image('effect_slow', (1469, 6 * 30 + 10))
                if e2.shield:
                    mods.Iris.draw_image('effect_shield', (1469, 8 * 30 + 10))
                if e2.slow >= 0:
                    mods.Iris.draw_image('effect_slow', (1469, 9 * 30 + 10))
                if e3.shield:
                    mods.Iris.draw_image('effect_shield', (1469, 11 * 30 + 10))
                if e3.slow >= 0:
                    mods.Iris.draw_image('effect_slow', (1469, 12 * 30 + 10))
                
                # Scores in level mode
                if GB_NOW == GB_LEVEL:
                    mods.printlib.OutTextxy(
                        f"E0: {ST_LEVELGOALS.scores[0]} E1: {ST_LEVELGOALS.scores[1]} E2: {ST_LEVELGOALS.scores[2]} E3: {ST_LEVELGOALS.scores[3]}", (255, 120, 8),
                        ('right', 14)
                    )
                    mods.printlib.OutTextxy(
                        f"Lvl: {GB_LEVEL_LVL} / 25", (8, 120, 255), ('right', 15)
                    )
                mods.printlib.xyprint(f'Facing: {pl.facing}', (255, 255, 255), ('right', 12))
                
                #mods.printlib.xyprint(f'CPU: {cpu_per}  Memory: {mem_per}', (0, 255, 0), ('right', 17))
            
            FPS_Tick = MAIN_TPS + 100
        
        if first_run == True and not mods.cfg.SimplifyOutputInformation:
            printm('01 judge finished.')
        
        kp = pygame.key.get_pressed()
        if first_run == True and not mods.cfg.SimplifyOutputInformation:
            printm('获取按键信息。')
        if kp[pygame.K_p]:
            mods.CrystalLib.Pause()
            Redraw()
        
        if MAIN_TPS > MovTNT_TPS:
            MovTNT(pl, e1, e2, e3)
            MovTNT_TPS = MAIN_TPS + 50
        if MAIN_TPS > Mov_Rocket_TPS:
            Mov_Rocket_TPS = MAIN_TPS + 20
            if len(RocketList) != 0:
                new_rocketlist = []
                for r in RocketList:
                    MovRocket(r, pl, e1, e2, e3)
                    if r.live == True:
                        new_rocketlist.append(r)
                    else:
                        pass
                RocketList = new_rocketlist
        
        if MAIN_TPS>= pl.nextTps:
            if pl.health <= 0:
                if pl.rebirth < 0:
                    #pl.rebirth = 480 if pl.shield == False else 1
                    if pl.shield == True:
                        pl.rebirth = 1
                        pl.shield = False
                    else:
                        pl.HasAlreadyDied = True
                    pl.died += 1
                    #pl.goal = 0
                elif pl.rebirth == 0:
                    if pl.HasAlreadyDied == False:
                        pl.x = 16
                        pl.y = 11
                        pl.health = 1
                        pl.rebirth = -1
                        pl.xp = 0
                        pl.yp = 0
                        pl.using_b = False
                        if not pl.shield:
                            pl.goal = 0
                            pl.frozen = False
                            pl.slow = -1
                            pl.speed = mods.cfg.player_speed()
                        else:
                            pl.shield = False
                    else:
                        pass
                else:
                    pl.rebirth -= 1
            
            if pl.xp > 0:
                pl.xp += pl.speed
                if pl.xp > 60:
                    pl.xp = 0
                    pl.x += 1
            elif pl.yp > 0:
                pl.yp += pl.speed
                if pl.yp > 60:
                    pl.yp = 0
                    pl.y += 1
            elif pl.xp < 0:
                pl.xp -= pl.speed
                if pl.xp < -60:
                    pl.xp = 0
                    pl.x -= 1
            elif pl.yp < 0:
                pl.yp -= pl.speed
                if pl.yp < -60:
                    pl.yp = 0
                    pl.y -= 1
            else:
                if kp[pygame.K_UP] and cango(pl, 'north') and pl.health > 0 and not pl.frozen:
                    pl.yp = -1
                elif kp[pygame.K_DOWN] and cango(pl, 'south') and pl.health > 0 and not pl.frozen:
                    pl.yp = 1
                elif kp[pygame.K_LEFT] and cango(pl, 'west') and pl.health > 0 and not pl.frozen:
                    pl.xp = -1
                elif kp[pygame.K_RIGHT] and cango(pl, 'east') and pl.health > 0 and not pl.frozen:
                    pl.xp = 1
            if pl.health > 0 and not pl.frozen:
                if kp[pygame.K_UP]:
                    pl.facing = 'north'
                elif kp[pygame.K_DOWN]:
                    pl.facing = 'south'
                elif kp[pygame.K_LEFT]:
                    pl.facing = 'west'
                elif kp[pygame.K_RIGHT]:
                    pl.facing = 'east'
            if pl.tnt_timeout > 0:
                pl.tnt_timeout -= 1
            if pl.rocket_timeout > 0:
                pl.rocket_timeout -= 1
            if kp[pygame.K_KP2] and pl.rocket_timeout <= 0 and pl.rocket > 0 and pl.health > 0 and not pl.frozen:
                pl.rocket_timeout = 32
                AddRocket(
                    pl.x, pl.y, pl.facing, pl.ID
                )
                pl.rocket -= 1
            if kp[pygame.K_KP1] and pl.hold_mine > 0 and pl.health > 0 and not pl.frozen and HaveMine(pl.x, pl.y)==False:
                Try_SetLandMine(pl)
            if kp[pygame.K_KP0] and pl.tnt_timeout <= 0 and pl.hold_tnt >0 and pl.health > 0 and not pl.frozen:
                pl.tnt_timeout = 40
                if pl.facing == 'west':
                    Create_TNT(TNT(
                        y = pl.y, x = max(0, pl.x - mods.cfg.TNTPower), belong = 0,Time=MAIN_TPS+1_600,
                        xFrom=pl.x, yFrom=pl.y
                    ))
                    pl.hold_tnt -= 1
                elif pl.facing == 'east':
                    Create_TNT(TNT(
                        y = pl.y, x = min(19, pl.x + mods.cfg.TNTPower), belong = 0,Time=MAIN_TPS+1_600,
                        xFrom=pl.x, yFrom=pl.y
                    ))
                    pl.hold_tnt -= 1
                elif pl.facing == 'south':
                    Create_TNT(TNT(
                        x = pl.x, y = min(14, pl.y + mods.cfg.TNTPower), belong = 0,Time=MAIN_TPS+1_600,
                        xFrom=pl.x, yFrom=pl.y
                    ))
                    pl.hold_tnt -= 1
                else:
                    Create_TNT(TNT(
                        x = pl.x, y = max(0, pl.y - mods.cfg.TNTPower), belong = 0,Time=MAIN_TPS+1_600,
                        xFrom=pl.x, yFrom=pl.y
                    ))
                    pl.hold_tnt -= 1
            
            if kp[pygame.K_KP_ENTER] and pl.capacity > 0 and not hasb(pl.x, pl.y) and pl.health > 0 and Map[pl.y][pl.x].mtype == Void and not pl.frozen:
                CreateBomb(Bomb(x = pl.x, y = pl.y, Time = MAIN_TPS + 2_200, belong = 0, color = (50, 255, 255), power = pl.power))
                pl.capacity -= 1
            #if kp[pygame.K_b] and Map[pl.y][pl.x].mtype == Void and pl.health > 0 and not pl.frozen:
            #    Map[pl.y][pl.x].mtype = Soft
            
            pl.nextTps = MAIN_TPS + 10
            
            # 20240103
            #eat
            match Map[pl.y][pl.x].addon:
                case mods.Iris.MA_NULL:
                    pass
                case mods.Iris.MA_APPLE:
                    pl.capacity += 1
                    Map[pl.y][pl.x].addon = mods.Iris.MA_NULL
                    if GB_NOW == GB_LEVEL:
                        ST_LEVELGOALS.scores[pl.ID] += 20
                case mods.Iris.MA_POWER:
                    pl.power += 1
                    Map[pl.y][pl.x].addon = mods.Iris.MA_NULL
                    if GB_NOW == GB_LEVEL:
                        ST_LEVELGOALS.scores[pl.ID] += 20
                case mods.Iris.MA_TNT:
                    pl.hold_tnt += 1
                    Map[pl.y][pl.x].addon = mods.Iris.MA_NULL
                    if GB_NOW == GB_LEVEL:
                        ST_LEVELGOALS.scores[pl.ID] += 20
                case mods.Iris.MA_SHIELD:
                    pl.shield = True
                    Map[pl.y][pl.x].addon = mods.Iris.MA_NULL
                    if GB_NOW == GB_LEVEL:
                        ST_LEVELGOALS.scores[pl.ID] += 20
                case mods.Iris.MA_ROCKET:
                    pl.rocket += 1
                    Map[pl.y][pl.x].addon = mods.Iris.MA_NULL
                    if GB_NOW == GB_LEVEL:
                        ST_LEVELGOALS.scores[pl.ID] += 20
                case mods.Iris.MA_DIAMOND:
                    Map[pl.y][pl.x].addon = mods.Iris.MA_NULL
                    if GB_NOW == GB_LEVEL:
                        ST_LEVELGOALS.scores[pl.ID] += 100
                case mods.Iris.MA_LANDMINE:
                    pl.hold_mine += 1
                    Map[pl.y][pl.x].addon = mods.Iris.MA_NULL
                    if GB_NOW == GB_LEVEL:
                        ST_LEVELGOALS.scores[pl.ID] += 20
                case _:
                    pass
            
            # **************** &&& ******************
            # Double Mode: e1 = Player 2
            if e1.health <= 0:
                if e1.rebirth < 0:
                    #e1.rebirth = 480 if e1.shield == False else 1
                    if e1.shield == True:
                        e1.shield = False
                        e1.rebirth = 1
                    else:
                        e1.HasAlreadyDied = True
                    e1.died += 1
                    #e1.goal = 0
                elif e1.rebirth == 0:
                    if e1.HasAlreadyDied == False:
                        e1.x = 3
                        e1.y = 3
                        e1.health = 1
                        e1.rebirth = -1
                        e1.xp = 0
                        e1.yp = 0
                        e1.using_b = False
                        if not e1.shield:
                            e1.goal = 0
                            e1.frozen = False
                            e1.slow = -1
                            e1.speed = mods.cfg.player_speed()
                        else:
                            e1.shield = False
                    else:
                        pass
                else:
                    e1.rebirth -= 1
            
            if e1.xp > 0:
                e1.xp += e1.speed
                if e1.xp > 60:
                    e1.xp = 0
                    e1.x += 1
            elif e1.yp > 0:
                e1.yp += e1.speed
                if e1.yp > 60:
                    e1.yp = 0
                    e1.y += 1
            elif e1.xp < 0:
                e1.xp -= e1.speed
                if e1.xp < -60:
                    e1.xp = 0
                    e1.x -= 1
            elif e1.yp < 0:
                e1.yp -= e1.speed
                if e1.yp < -60:
                    e1.yp = 0
                    e1.y -= 1
            else:
                if kp[pygame.K_w] and cango(e1, 'north') and e1.health > 0 and not e1.frozen:
                    e1.yp = -1
                elif kp[pygame.K_s] and cango(e1, 'south') and e1.health > 0 and not e1.frozen:
                    e1.yp = 1
                elif kp[pygame.K_a] and cango(e1, 'west') and e1.health > 0 and not e1.frozen:
                    e1.xp = -1
                elif kp[pygame.K_d] and cango(e1, 'east') and e1.health > 0 and not e1.frozen:
                    e1.xp = 1
            if e1.health > 0 and not e1.frozen:
                if kp[pygame.K_w]:
                    e1.facing = 'north'
                elif kp[pygame.K_s]:
                    e1.facing = 'south'
                elif kp[pygame.K_a]:
                    e1.facing = 'west'
                elif kp[pygame.K_d]:
                    e1.facing = 'east'
            if e1.tnt_timeout > 0:
                e1.tnt_timeout -= 1
            if e1.rocket_timeout > 0:
                e1.rocket_timeout -= 1
            if kp[pygame.K_r] and e1.rocket_timeout <= 0 and e1.rocket > 0 and e1.health > 0 and not e1.frozen:
                e1.rocket_timeout = 32
                AddRocket(
                    e1.x, e1.y, e1.facing, e1.ID
                )
                e1.rocket -= 1
            if kp[pygame.K_y] and e1.hold_mine > 0 and e1.health > 0 and not e1.frozen and HaveMine(e1.x, e1.y)==False:
                Try_SetLandMine(e1)
            if kp[pygame.K_t] and e1.tnt_timeout <= 0 and e1.hold_tnt >0 and e1.health > 0 and not e1.frozen:
                e1.tnt_timeout = 40
                if e1.facing == 'west':
                    Create_TNT(TNT(
                        y = e1.y, x = max(0, e1.x - mods.cfg.TNTPower), belong = 1,Time=MAIN_TPS+1_600,
                        xFrom=e1.x, yFrom=e1.y
                    ))
                    e1.hold_tnt -= 1
                elif e1.facing == 'east':
                    Create_TNT(TNT(
                        y = e1.y, x = min(19, e1.x + mods.cfg.TNTPower), belong = 1,Time=MAIN_TPS+1_600,
                        xFrom=e1.x, yFrom=e1.y
                    ))
                    e1.hold_tnt -= 1
                elif e1.facing == 'south':
                    Create_TNT(TNT(
                        x = e1.x, y = min(14, e1.y + mods.cfg.TNTPower), belong = 1,Time=MAIN_TPS+1_600,
                        xFrom=e1.x, yFrom=e1.y
                    ))
                    e1.hold_tnt -= 1
                else:
                    Create_TNT(TNT(
                        x = e1.x, y = max(0, e1.y - mods.cfg.TNTPower), belong = 1,Time=MAIN_TPS+1_600,
                        xFrom=e1.x, yFrom=e1.y
                    ))
                    e1.hold_tnt -= 1
            
            if kp[pygame.K_SPACE] and e1.capacity > 0 and not hasb(e1.x, e1.y) and e1.health > 0 and Map[e1.y][e1.x].mtype == Void and not e1.frozen:
                CreateBomb(Bomb(x = e1.x, y = e1.y, Time = MAIN_TPS + 2_200, belong = 1, color = (50, 255, 255), power = e1.power))
                e1.capacity -= 1
            #if kp[pygame.K_b] and Map[pl.y][pl.x].mtype == Void and pl.health > 0 and not pl.frozen:
            #    Map[pl.y][pl.x].mtype = Soft
            
            pl.nextTps = MAIN_TPS + 10
            
            # 20240103
            #eat
            match Map[e1.y][e1.x].addon:
                case mods.Iris.MA_NULL:
                    pass
                case mods.Iris.MA_APPLE:
                    e1.capacity += 1
                    Map[e1.y][e1.x].addon = mods.Iris.MA_NULL
                    if GB_NOW == GB_LEVEL:
                        ST_LEVELGOALS.scores[e1.ID] += 20
                case mods.Iris.MA_POWER:
                    e1.power += 1
                    Map[e1.y][e1.x].addon = mods.Iris.MA_NULL
                    if GB_NOW == GB_LEVEL:
                        ST_LEVELGOALS.scores[e1.ID] += 20
                case mods.Iris.MA_TNT:
                    e1.hold_tnt += 1
                    Map[e1.y][e1.x].addon = mods.Iris.MA_NULL
                    if GB_NOW == GB_LEVEL:
                        ST_LEVELGOALS.scores[e1.ID] += 20
                case mods.Iris.MA_SHIELD:
                    e1.shield = True
                    Map[e1.y][e1.x].addon = mods.Iris.MA_NULL
                    
                    if GB_NOW == GB_LEVEL:
                        ST_LEVELGOALS.scores[e1.ID] += 20
                case mods.Iris.MA_ROCKET:
                    e1.rocket += 1
                    Map[e1.y][e1.x].addon = mods.Iris.MA_NULL
                    if GB_NOW == GB_LEVEL:
                        ST_LEVELGOALS.scores[e1.ID] += 20
                case mods.Iris.MA_DIAMOND:
                    Map[e1.y][e1.x].addon = mods.Iris.MA_NULL
                    if GB_NOW == GB_LEVEL:
                        ST_LEVELGOALS.scores[e1.ID] += 100
                case mods.Iris.MA_LANDMINE:
                    e1.hold_mine += 1
                    Map[e1.y][e1.x].addon = mods.Iris.MA_NULL
                    if GB_NOW == GB_LEVEL:
                        ST_LEVELGOALS.scores[e1.ID] += 20
                case _:
                    pass
        if first_run and not mods.cfg.SimplifyOutputInformation:
            printm('获取Player操作信息。')
        
        #
        
        # 渲染顺序：
        # entity_around
        # MovBomb (Bomb_around + Bomb + bbs)
        # entity
        
        #
        
        # New (slower, but no bugs?)
        #for x in range(0, 20):
        #    for y in range(0, 15):
        #        draw_block(x, y, Map[y][x].mtype)
        Shaders.quickdraw.DrawBlocks(Map)
        
        # In new render plan, there's no need forus to render entities's env anymore!
        #MovBomb(pl, e1, e2, e3, isOnlyRender = True)
        if MAIN_TPS >= MovIceThornTps:
            movIceThorn(tuple([pl, e1, e2, e3]), False)
            MovIceThornTps = MAIN_TPS + 50
        else:
            movIceThorn(tuple([pl, e1, e2, e3]), isOnlyRender = True)
        pl.mov_effect()
        if pl.ender_pearl_timeout != 0:
            mods.Iris.draw_particle_enderpearl(pl.x * 60 + pl.xp, pl.y * 60 + pl.yp)
        #if TNTList != []: //20240103 改变渲染顺序
        #    for t in TNTList:
        #        mods.Iris.draw_item(t)
        
        if first_run == True and not mods.cfg.SimplifyOutputInformation:
            printm('地图更新完成。')
        
        if Title_Bar[0] != None:
            mods.printlib.TitleBarPrint(Title_Bar[0])
            #for x in range(5, 15):
            #    for y in range(6, 12):
            #        draw_block(x, y, Map[y][x].mtype) # 这个东西会导致炸弹显示不出来!!!!!!!!!!!!!!!!!!
            if MAIN_TPS > Title_Bar[1]:
                Title_Bar[0] = None
                #Redraw()
        
        MovBombNeo(pl, e1, e2, e3)
        
        #for x in range(0, 20):
        #    for y in range(0, 15):
        #        mods.Iris. draw_addon(x, y, Map[y][x].addon)
        Shaders.quickdraw.DrawAddons(Map)
        if TNTList != []: # //20240103 改变渲染顺序
            for t in TNTList:
                mods.Iris.draw_item(t)
        if len(RocketList) != 0:
            for r in RocketList:
                mods.Iris.draw_item(r)
        
        power_max = max(
            pl.power if pl.health > 0 else 2,
            e1.power if e1.health > 0 else 2,
            e2.power if e2.health > 0 else 2,
            e3.power if e3.health > 0 else 2
        )
        #if ftps() >= e1.nextTps:
        #    e1.run(pl, power_max, e2, e3)
        if len(MineList) != 0:
            MoveMine((pl, e1, e2, e3))
            for lm in MineList:
                mods.Iris.draw_item(lm)
        
        if e1.health > 0:
            mods.Iris.draw_entity(e1, screen)
            if e1.ender_pearl_timeout != 0:
                mods.Iris.draw_particle_enderpearl(e1.x * 60 + e1.xp, e1.y * 60 + e1.yp)
        if MAIN_TPS >= e2.nextTps:
            e2.run(pl, power_max, e1, e3)
        if e2.health > 0:
            mods.Iris.draw_entity(e2, screen)
            if e1.ender_pearl_timeout != 0:
                mods.Iris.draw_particle_enderpearl(e1.x * 60 + e1.xp, e1.y * 60 + e1.yp)
        if MAIN_TPS >= e3.nextTps:
            e3.run(pl, power_max, e1, e2)
        if e3.health > 0:
            mods.Iris.draw_entity(e3, screen)
            if e1.ender_pearl_timeout != 0:
                mods.Iris.draw_particle_enderpearl(e1.x * 60 + e1.xp, e1.y * 60 + e1.yp)
        if first_run and not mods.cfg.SimplifyOutputInformation:
            printm('获取NPC信息。')
        if pl.health > 0:
            mods.Iris.draw_entity(pl, screen)
        if first_run == True:
            if not mods.cfg.SimplifyOutputInformation:
                printm('绘制完成。')
            printm('循环检查完成。')
            first_run = False
        pygame.display.update()
        

def normal():
    Ellipsis
    #global screen
    #while True:
    #    fps.tick(102)

def hard():
    Ellipsis
    #global screen
    #while True:
    #    fps.tick(102)

#1

Hello_text = tuple(
    ['Hello world!', 'Also try TwilightForest?', 'Happy happy happy!', 'Do you love fire and light?', #old
    '元旦快乐！', 'Happy New Year!' # 20240103
    ]
)
f1=pygame.freetype.SysFont('华文仿宋',0x16)
freeRect = f1.render_to(screen, (720, 500), rd.choice(Hello_text),
                        fgcolor = mods.CrystalLib.RandomColor(), rotation=35)
if mods.cfg.EnableVerticalSynchronization == True:
    pygame.display.set_mode(vsync = True)
printm('Success loading base.')
printm('成功进入主循环。')

#while True:
#    fps.tick(10)
#    
def Help_Win():
    cstr = ("方向键: 移动", "_"*20, "技能: ", "WASD: 瞬移", "T: TNT", "I: Ice spike", "B: Block", "SPACE: Bomb")
    fstr = tuple(f.render(i, True, (0, 0, 0)) for i in cstr)
    while True:
        screen.fill((255, 255, 255))
        y = 20
        for j in fstr:
            screen.blit(j, (69, y))
            y += 40
        pygame.display.flip()
        for ev in pygame.event.get():
            if ev.type == pygame.QUIT:
                return
            elif ev.type == pygame.KEYDOWN:
                match ev.key:
                    case pygame.K_ESCAPE:
                        return
                    case pygame.K_SPACE:
                        # plan1.10: set key
                        return
                    case _:
                        pass

def main():
    global WARN_TOTAL, GM_NOW
    GM_NOW = GM_BEDWAR
    try:
        reLoad()
        printm('地图初始化完成。')
        bedwar()
    except ExitGame as e:
        printm(f'退出游戏: {e}.')
    except Exception:
        WARN_TOTAL -= 1
        print_warning(f'A serious Exception occured: \n{traceback.format_exc()}')
        pygame.time.wait(10_000)
    finally:
        return

def start_Normal():
    global WARN_TOTAL, GM_NOW
    GM_NOW = GM_NORMAL
    #friend = mods.cfg.FriendFire
    try:
        reLoad()
        printm('地图初始化完成。')
        #mods.cfg.FriendFire = False
        Normal()
    except ExitGame as e:
        printm(f'退出游戏: {e}.')
    except Exception:
        WARN_TOTAL -= 1
        print_warning(f'A serious Exception occured: \n{traceback.format_exc()}')
        pygame.time.wait(8_00)
    finally:
        Monitor.close()
        return

def start_DoubleNormal(isPVP: bool):
    global WARN_TOTAL, GM_NOW
    GM_NOW = GM_NORMAL
    friend = mods.cfg.FriendFire
    try:
        reLoad()
        printm('地图初始化完成。')
        mods.cfg.FriendFire = True
        Monitor.start()
        Double_Normal(isPVP)
        Monitor.close()
        mods.cfg.FriendFire = friend
    except ExitGame as e:
        printm(f'退出游戏: {e}.')
        Monitor.close()
    except Exception:
        WARN_TOTAL -= 1
        print_warning(f'A serious Exception occured: \n{traceback.format_exc()}')
        Monitor.close()
        pygame.time.wait(10_000)
    finally:
        return


class QB_MINIGAMES(object):
    def __init__(self, _name: str, _desc: str, _feature: dict, _pass: bool = False) -> None:
        self.name: str = _name
        self.desc : str = _desc
        self.feature : dict = _feature
        self.ok: bool = _pass
        assert(type(self.name) == str)
        assert(type(self.desc) == str)
        assert(type(self.feature) == dict)
        assert(type(self.ok) == bool)

# 1.16.1
qbgame_invisiblewar = QB_MINIGAMES(
    '隐形刺客', '敌人们拥有隐身衣, 保护自己并消灭他们吧!', {
        "enemy_invisible": 320_269 # 1.16.6 -1->limited
    }
)
def qbgame_switchplace_fn(pl: Player, e1: Enemy, e2: Enemy, e3: Enemy):
    tup = (pl, e1, e2, e3)
    if prob(40):
        # 40%
        li = rd.sample(tup, 2)
        a, b = li[0], li[1]
        a.x, a.xp, a.y, a.yp, b.x, b.xp, b.y, b.yp = b.x, b.xp, b.y, b.yp, a.x, a.xp, a.y, a.yp
    elif prob(30):
        # 18%
        li = rd.sample(tup, 2)
        a, b = li[0], li[1]
        a.x, a.xp, a.y, a.yp, b.x, b.xp, b.y, b.yp = b.x, b.xp, b.y, b.yp, a.x, a.xp, a.y, a.yp
        li = rd.sample(tup, 2)
        a, b = li[0], li[1]
        a.x, a.xp, a.y, a.yp, b.x, b.xp, b.y, b.yp = b.x, b.xp, b.y, b.yp, a.x, a.xp, a.y, a.yp
        li = rd.sample(tup, 2)
        a, b = li[0], li[1]
        a.x, a.xp, a.y, a.yp, b.x, b.xp, b.y, b.yp = b.x, b.xp, b.y, b.yp, a.x, a.xp, a.y, a.yp
    else:
        # 42%
        pass
qbgame_switchplace = QB_MINIGAMES(
    '换位思考', '每隔一段时间随机交换场上实体的位置', {
        "tick_operate": {
            18_000: qbgame_switchplace_fn
        }
    }
)
def qbgame_rocketrace_fn(*args):
    e: Enemy | Player
    for e in args:
        e.hold_tnt += 1
        e.rocket += 10
        # 1.16.6 easier
        if e.health > 0:
            e.shield = True
        if type(e) != Player:
            e.hold_tnt += 2
qbgame_rocketrace = QB_MINIGAMES(
    '火箭竞速', '数不尽的火箭与炸药！', {
        "tick_operate": {
            24_000: qbgame_rocketrace_fn
        }
    }
)
def qbgame_tntkiller_fn(pl: Player, e1: Enemy, e2: Enemy, e3: Enemy):
    Create_TNT(TNT(
        pl.x, pl.y, 0, Time=ftps()+1600, xFrom=10, yFrom=7
    ))
qbgame_tntkiller = QB_MINIGAMES(
    '疯狂的TNT', 'TNT会不断地向你袭来', {
        "tick_operate": {
            12_560: qbgame_tntkiller_fn
        }
    }
)

IMAGE: typing.TypeAlias = pygame.surface.Surface
def qbgame_eatapple_fn():
    raise NotImplementedError() #241028
    global screen
    pl_x : int = 600
    pl_y : int = 860
    itemlist: list[tuple[IMAGE, int, int]] = [] #(id, x, y)
    ITEMSPEED: int = 4
    TICKNOW: int = ftps()
    PLTICK : int = TICKNOW
    ITEMTICK:int = TICKNOW
    ADDITEMTICK : int = TICKNOW
    AddTimeMax: int = 6090
    AddTimeMax_Count: int = 0
    IM_APPLE: IMAGE = mods.Iris.vanilla._apple
    IM_TNT: IMAGE = mods.Iris.Shaders.jade._item_tnt
    IM_SHIELD: IMAGE = mods.Iris.Shaders.jade._addonShield
    IM_POWER: IMAGE = mods.Iris.Shaders.jade._power
    IM_DIAMOND: IMAGE = mods.Iris.Shaders.jade._addonDiamond
    while True:
        fps.tick(120)
        TICKNOW = ftps()
        if TICKNOW >= PLTICK:
            PLTICK = TICKNOW + 20
            kp = pygame.key.get_pressed()
            if kp[pygame.K_LEFT]:
                pl_x -= 7
            elif kp[pygame.K_RIGHT]:
                pl_x += 7
            if pl_x < 1:
                pl_x = 1
            elif pl_x > 1200:
                pl_x = 1200
        if TICKNOW >= ADDITEMTICK:
            ADDITEMTICK = TICKNOW + rd.randint(900, AddTimeMax)
            AddTimeMax_Count += 1
            if AddTimeMax > 1200 and AddTimeMax_Count % 12 == 0:
                AddTimeMax -= rd.randint(120, 360)
                if AddTimeMax < 1200:
                    AddTimeMax = 1200
            if prob(2):
                if prob(2):
                    ITEMSPEED += 1
            # apple(30) tnt(45) shield(12) power(12) diamond(1)
            for _ in range(rd.randint(3, 10)):
                if prob(30):
                    itemlist.append((IM_APPLE, rd.randint(50, 1160), 0))
                elif prob(64):
                    itemlist.append((IM_TNT, rd.randint(50, 1160), 0))
                elif prob(48):
                    itemlist.append((IM_SHIELD, rd.randint(50, 1160), 0))
                elif prob(93):
                    itemlist.append((IM_POWER, rd.randint(50, 1160), 0))
                else:
                    itemlist.append((IM_DIAMOND, rd.randint(50, 1160), 0))
        if TICKNOW >= ITEMTICK:
            ITEMTICK = TICKNOW + 20
            if len(itemlist) != 0:
                item_new = []
                for ie in itemlist:
                    ie[2] += ITEMSPEED

qbgame_final_list: tuple[QB_MINIGAMES] = (
    qbgame_switchplace, qbgame_invisiblewar, qbgame_rocketrace, qbgame_tntkiller
)
qbgame_final_list_pos: list[tuple[int, int, int, int]] = []
def _qbgame_final_list_pos_pack():
    global qbgame_final_list_pos
    _x: int = 50
    _y: int = 50
    for _ in range(len(qbgame_final_list)):
        qbgame_final_list_pos.append((_x, _y, 150, 150))
        _x += 200
        if _x > 1200:
            _x = 50
            _y += 200
_qbgame_final_list_pos_pack()

def qb_minigamestarttips(name:str, desc:str):
    fname = f.render(name, True, mods.CrystalLib.RandomColor())
    fdesc = f.render(desc, True, mods.CrystalLib.RandomColor())
    while True:
        fps.tick(10)
        screen.fill((198,198,198))
        r = fname.get_rect()
        r.center = (600, 400)
        screen.blit(fname, r)
        r = fdesc.get_rect()
        r.center = (600, 560)
        screen.blit(fdesc, r)
        for ev in pygame.event.get():
            if ev.type in (pygame.QUIT, pygame.KEYDOWN, pygame.MOUSEBUTTONDOWN):
                return
        pygame.display.flip()
        
def qb_minigames():
    global GM_NOW
    GM_NOW = GM_NORMAL
    while True:
        fps.tick(10)
        screen.fill((255, 255, 255))
        for i in range(len(qbgame_final_list)):
            if qbgame_final_list[i].ok:
                pygame.draw.rect(screen, (0, 255, 96), qbgame_final_list_pos[i], 3)
            else:
                pygame.draw.rect(screen, (168, 128, 156), qbgame_final_list_pos[i])
            fs = f.render(qbgame_final_list[i].desc, True, mods.CrystalLib.RandomColor())
            r = fs.get_rect()
            r.center = (qbgame_final_list_pos[i][0]+75, qbgame_final_list_pos[i][1]+50)
            screen.blit(fs, r)
            fs = f.render(qbgame_final_list[i].name, True, mods.CrystalLib.RandomColor())
            r = fs.get_rect()
            r.center = (qbgame_final_list_pos[i][0]+75, qbgame_final_list_pos[i][1]+100)
            screen.blit(fs, r)
        pygame.display.flip()
        for ev in pygame.event.get():
            if ev.type == pygame.QUIT:
                return
            elif ev.type == pygame.KEYDOWN:
                match ev.key:
                    case pygame.K_ESCAPE:
                        return
                    case _:
                        pass
            elif ev.type == pygame.MOUSEBUTTONDOWN:
                xp, yp = ev.pos
                for i in range(len(qbgame_final_list)):
                    if qbgame_final_list_pos[i][0] <= xp <= qbgame_final_list_pos[i][0]+150\
                    and qbgame_final_list_pos[i][1] <= yp <= qbgame_final_list_pos[i][1]+150:
                        qb_minigamestarttips(qbgame_final_list[i].name,qbgame_final_list[i].desc)
                        printm(f'启动小游戏{i}')
                        pygame.event.clear()
                        if template(qbgame_final_list[i].feature):
                            qbgame_final_list[i].ok = True
                        pygame.event.clear()
                        printm(f'finish.')

Start_Screen_Text = f.render('Press R/N/D to start new game, J for help.', True, (0,0,255))
Start_Screen_Text_2 = f.render(f'QB {_VERSION_}. {_MODS_COUNT_} mods loaded.', True, (0,0,255)) # 1.16.2 add version
SurfBedWar = pygame.Surface((200, 69))
SurfBedWarDB  = pygame.Surface((200, 69))
SurfNormal = pygame.Surface((200, 69))
SurfNormalDB = pygame.Surface((200, 69))

SurfBedWar.fill((255, 255, 255))
pygame.draw.rect(SurfBedWar, mods.CrystalLib.RandomColor(), (0, 0, 199, 68), 4)
tsurf = f.render('BedWar', True, mods.CrystalLib.RandomDarkColor())
tsurfRect = tsurf.get_rect()
tsurfRect.center = (100, 34)
SurfBedWar.blit(tsurf, tsurfRect)

SurfBedWarDB.fill((255, 255, 255))
pygame.draw.rect(SurfBedWarDB, mods.CrystalLib.RandomColor(), (0, 0, 199, 68), 4)
tsurf = f.render('PVP', True, mods.CrystalLib.RandomDarkColor())
tsurfRect = tsurf.get_rect()
tsurfRect.center = (100, 34)
SurfBedWarDB.blit(tsurf, tsurfRect)

SurfNormal.fill((255, 255, 255))
pygame.draw.rect(SurfNormal, mods.CrystalLib.RandomColor(), (0, 0, 199, 68), 4)
tsurf = f.render('Normal', True, mods.CrystalLib.RandomDarkColor())
tsurfRect = tsurf.get_rect()
tsurfRect.center = (100, 34)
SurfNormal.blit(tsurf, tsurfRect)

SurfNormalDB.fill((255, 255, 255))
pygame.draw.rect(SurfNormalDB, mods.CrystalLib.RandomColor(), (0, 0, 199, 68), 4)
tsurf = f.render('Normal(db)', True, mods.CrystalLib.RandomDarkColor())
tsurfRect = tsurf.get_rect()
tsurfRect.center = (100, 34)
SurfNormalDB.blit(tsurf, tsurfRect)

SurfLevel = pygame.Surface((200, 69))
SurfLevel.fill((255, 255, 255))
pygame.draw.rect(SurfLevel, mods.CrystalLib.RandomColor(), (0, 0, 199, 68), 4)
tsurf = f.render('Level', True, mods.CrystalLib.RandomDarkColor())
tsurfRect = tsurf.get_rect()
tsurfRect.center = (100, 34)
SurfLevel.blit(tsurf, tsurfRect)

SurfLevelDB = pygame.Surface((200, 69))
SurfLevelDB.fill((255, 255, 255))
pygame.draw.rect(SurfLevelDB, mods.CrystalLib.RandomColor(), (0, 0, 199, 68), 4)
tsurf = f.render('Level(db)', True, mods.CrystalLib.RandomDarkColor())
tsurfRect = tsurf.get_rect()
tsurfRect.center = (100, 34)
SurfLevelDB.blit(tsurf, tsurfRect)

SurfLevelPVP = pygame.Surface((200, 69))
SurfLevelPVP.fill((255, 255, 255))
pygame.draw.rect(SurfLevelPVP, mods.CrystalLib.RandomColor(), (0, 0, 199, 68), 4)
tsurf = f.render('PVP(level)', True, mods.CrystalLib.RandomDarkColor())
tsurfRect = tsurf.get_rect()
tsurfRect.center = (100, 34)
SurfLevelPVP.blit(tsurf, tsurfRect)

SurfHelpJ = pygame.Surface((200, 69))
SurfHelpJ.fill((255, 255, 255))
pygame.draw.rect(SurfHelpJ, mods.CrystalLib.RandomColor(), (0, 0, 199, 68), 4)
tsurf = f.render('Help', True, mods.CrystalLib.RandomDarkColor())
tsurfRect = tsurf.get_rect()
tsurfRect.center = (100, 34)
SurfHelpJ.blit(tsurf, tsurfRect)

SurfMinigame = pygame.Surface((60,60))
SurfMinigame.fill(mods.CrystalLib.RandomLightColor())
SurfMinigame.blit(f.render('小游戏', True, mods.CrystalLib.RandomDarkColor()), (0,0))

def WinMain():
    global GB_NOW, ST_LEVELGOALS, GB_LEVEL_SHUT, GB_LEVEL_LVL
    GB_NOW = 0
    pygame.display.set_mode((1500, 900), vsync = mods.cfg.EnableVerticalSynchronization)
    HelloText = rd.choice(Hello_text)
    while True:
        fps.tick(10)
        pygame.draw.rect(screen, (255,255,0xff), (0,0,1199, 900))
        screen.blit(Start_Screen_Text, (450, 450+369))
        screen.blit(Start_Screen_Text_2, (450, 480+369))
        if NewVersionTip.isNewTry == True:
            #New_version_text = f.render(NewVersionTip.string, True, (0, 255, 0))
            #screen.blit(New_version_text, (450, 520))
            mods.printlib.OutTextxy(NewVersionTip.string, color = (0, 255, 0), place = ('center', 2))
        # 20240104
        screen.blit(SurfBedWar, (375, 300))
        screen.blit(SurfBedWarDB, (625, 300))
        screen.blit(SurfNormal, (375, 400))
        screen.blit(SurfNormalDB, (625, 400))
        screen.blit(SurfLevel, (375, 500))
        screen.blit(SurfLevelDB, (625, 500))
        screen.blit(SurfHelpJ, (375, 600))
        screen.blit(SurfLevelPVP, (625, 600))
        screen.blit(SurfMinigame, (10, 800))
        
        f1.render_to(screen, (900, 160), HelloText,fgcolor = mods.CrystalLib.RandomColor(), rotation=30)
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                return
            if event.type == pygame.KEYDOWN:
                match event.key:
                    case pygame.K_r:
                        main()
                        HelloText = rd.choice(Hello_text)
                        pygame.event.clear()
                    case pygame.K_c:
                        mods.cfg.change_config(screen)
                    case pygame.K_h:
                        Help_Win()
                    case pygame.K_n:
                        start_Normal()
                        HelloText = rd.choice(Hello_text)
                        pygame.event.clear()
                    case pygame.K_d:
                        start_DoubleNormal(False)
                        HelloText = rd.choice(Hello_text)
                        pygame.event.clear()
                    #case pygame.K_p:
                    #    start_DoubleNormal(True)
                    #    HelloText = rd.choice(Hello_text)
                    #    pygame.event.clear()
                    case pygame.K_j:
                        mods.Iris.Illustrated()
            elif event.type == pygame.MOUSEBUTTONDOWN:
                xp, yp = event.pos
                if 375 <= xp <= 575 and 300 <= yp <= 369:
                    main()
                    HelloText = rd.choice(Hello_text)
                    pygame.event.clear()
                elif 625 <= xp <= 825 and 300 <= yp <= 369:
                    start_DoubleNormal(True)
                    HelloText = rd.choice(Hello_text)
                    pygame.event.clear()
                elif 375 <= xp <= 575 and 400 <= yp <= 469:
                    start_Normal()
                    HelloText = rd.choice(Hello_text)
                    pygame.event.clear()
                elif 625 <= xp <= 825 and 400 <= yp <= 469:
                    start_DoubleNormal(False)
                    HelloText = rd.choice(Hello_text)
                    pygame.event.clear()
                elif 375 <= xp <= 575 and 500 <= yp <= 569:
                    GB_NOW = GB_LEVEL
                    ST_LEVELGOALS = LevelGoals()
                    GB_LEVEL_SHUT = False
                    GB_LEVEL_LVL = 0
                    for _ in range(25):
                        GB_LEVEL_LVL += 1
                        start_Normal()
                        pygame.event.clear()
                        i = True
                        tup = tuple(
                            f.render(
                                f"E{j}:  score = {ST_LEVELGOALS.scores[j]},  time = {ST_LEVELGOALS.timescore[j]},  total = {ST_LEVELGOALS.totalscore[j]}",
                                True, mods.CrystalLib.RandomColor()
                            ) for j in range(4)
                        )
                        while i:
                            pygame.time.wait(100)
                            pygame.draw.rect(screen, (255,255,255),(0,0,1200,900))
                            for _i in range(len(tup)):
                                screen.blit(tup[_i], (520, 369 + _i * 52))
                            pygame.display.flip()
                            for e in pygame.event.get():
                                if e.type == pygame.QUIT:
                                    i = False
                                if e.type == pygame.KEYDOWN and e.key in (pygame.K_ESCAPE,pygame.K_RETURN):
                                    i = False
                        if GB_LEVEL_SHUT == True:
                            break
                    if GB_LEVEL_SHUT == False:
                        tup = [
                            ('Player', ST_LEVELGOALS.totalscore[0],mods.CrystalLib.RandomColor()),
                            ('Enemy1', ST_LEVELGOALS.totalscore[1], mods.CrystalLib.RandomColor()),
                            ('Enemy2', ST_LEVELGOALS.totalscore[2], mods.CrystalLib.RandomColor()),
                            ('Enemy3', ST_LEVELGOALS.totalscore[3], mods.CrystalLib.RandomColor())
                        ]
                        tup.sort(key=lambda item: item[1],reverse=True)
                        _ = True
                        while _:
                            pygame.time.wait(100)
                            pygame.draw.rect(screen,(192,169,196),(0,0,1200,900))
                            for i in range(len(tup)):
                                ftup = f.render(f"第{i+1}名: {tup[i][0]}    score: {tup[i][1]}",True,tup[i][2])
                                screen.blit(ftup, (520, 369 + i*52))
                            pygame.display.flip()
                            for e in pygame.event.get():
                                if e.type == pygame.QUIT:
                                    _ = False
                                if e.type == pygame.KEYDOWN and e.key in (pygame.K_ESCAPE,):
                                    _ = False
                    GB_NOW = 0
                    HelloText = rd.choice(Hello_text)
                    pygame.event.clear()
                elif 625 <= xp <= 825 and 500 <= yp <= 569:
                    GB_NOW = GB_LEVEL
                    ST_LEVELGOALS = LevelGoals()
                    GB_LEVEL_SHUT = False
                    GB_LEVEL_LVL = 0
                    for _ in range(25):
                        GB_LEVEL_LVL += 1
                        start_DoubleNormal(False)
                        pygame.event.clear()
                        if GB_LEVEL_SHUT == True:
                            break
                    if GB_LEVEL_SHUT == False:
                        tup = [
                            ('Player', ST_LEVELGOALS.scores[0],mods.CrystalLib.RandomColor()),
                            ('Enemy1', ST_LEVELGOALS.scores[1], mods.CrystalLib.RandomColor()),
                            ('Enemy2', ST_LEVELGOALS.scores[2], mods.CrystalLib.RandomColor()),
                            ('Enemy3', ST_LEVELGOALS.scores[3], mods.CrystalLib.RandomColor())
                        ]
                        tup.sort(key=lambda item: item[1],reverse=True)
                        _ = True
                        while _:
                            pygame.time.wait(100)
                            pygame.draw.rect(screen,(192,169,196),(0,0,1200,900))
                            for i in range(len(tup)):
                                ftup = f.render(f"第{i+1}名: {tup[i][0]}    score: {tup[i][1]}",True,tup[i][2])
                                screen.blit(ftup, (520, 369 + i*52))
                            pygame.display.flip()
                            for e in pygame.event.get():
                                if e.type == pygame.QUIT:
                                    _ = False
                                if e.type == pygame.KEYDOWN and e.key in (pygame.K_ESCAPE,):
                                    _ = False
                    GB_NOW = 0
                    HelloText = rd.choice(Hello_text)
                    pygame.event.clear()
                elif 375 <= xp <= 575 and 600 <= yp <= 669:
                    mods.Iris.Illustrated()
                    pygame.event.clear()
                elif 625 <= xp <= 825 and 600 <= yp <= 669:
                    GB_NOW = GB_LEVEL
                    ST_LEVELGOALS = LevelGoals()
                    GB_LEVEL_SHUT = False
                    GB_LEVEL_LVL = 0
                    for _ in range(25):
                        GB_LEVEL_LVL += 1
                        start_DoubleNormal(True)
                        pygame.event.clear()
                        if GB_LEVEL_SHUT == True:
                            break
                    if GB_LEVEL_SHUT == False:
                        tup = [
                            ('Player1', ST_LEVELGOALS.scores[0],mods.CrystalLib.RandomColor()),
                            ('Player2', ST_LEVELGOALS.scores[1], mods.CrystalLib.RandomColor()),
                            #('Enemy2', ST_LEVELGOALS.scores[2], mods.CrystalLib.RandomColor()),
                            #('Enemy3', ST_LEVELGOALS.scores[3], mods.CrystalLib.RandomColor())
                        ]
                        tup.sort(key=lambda item: item[1],reverse=True)
                        _ = True
                        while _:
                            pygame.time.wait(100)
                            pygame.draw.rect(screen,(192,169,196),(0,0,1200,900))
                            for i in range(len(tup)):
                                ftup = f.render(f"第{i+1}名: {tup[i][0]}    score: {tup[i][1]}",True,tup[i][2])
                                screen.blit(ftup, (520, 369 + i*52))
                            pygame.display.flip()
                            for e in pygame.event.get():
                                if e.type == pygame.QUIT:
                                    _ = False
                                if e.type == pygame.KEYDOWN and e.key in (pygame.K_ESCAPE,):
                                    _ = False
                    GB_NOW = 0
                    HelloText = rd.choice(Hello_text)
                    pygame.event.clear()
                elif 10 <= xp <= 70 and 800 <= yp <= 860:
                    GB_NOW = GB_LEVEL
                    qb_minigames()
                    GB_NOW = 0
                    HelloText = rd.choice(Hello_text)
                    pygame.event.clear()
                else:
                    pass
        pygame.display.flip()

if __name__ == '__main__':
    try:
        WinMain()
    except Exception:
        printm(f'Game crashed!\n{traceback.format_exc()}')
    finally:
#os.system('Pause')
        pygame.quit()
        printm('关闭图形渲染窗口。')
        #printm('服务器连接已断开。')
        os.system('Pause')